Initial commit

This commit is contained in:
miaaurl
2026-06-11 16:40:31 +07:00
commit c321f2ba18
18 changed files with 6877 additions and 0 deletions

138
jalan_tanah/api/jalan.php Normal file
View File

@@ -0,0 +1,138 @@
<?php
// api/jalan.php — CRUD Data Jalan (GeoJSON LineString)
require_once '../config/database.php';
setCorsHeaders();
$method = $_SERVER['REQUEST_METHOD'];
$conn = getConnection();
switch ($method) {
case 'GET':
if (isset($_GET['id'])) {
$id = intval($_GET['id']);
$stmt = $conn->prepare("SELECT id, nama_jalan, status_jalan, panjang_meter, keterangan, geom, created_at, updated_at FROM data_jalan WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
if (!$row) { http_response_code(404); echo json_encode(['status'=>'error','message'=>'Data tidak ditemukan']); break; }
$row['koordinat'] = parseGeoJsonToLatLng($row['geom']);
unset($row['geom']);
echo json_encode(['status'=>'success','data'=>$row]);
} else {
// Filter support: ?status=Nasional&min_panjang=100&max_panjang=5000&q=nama
$where = ['1=1'];
$params = [];
$types = '';
if (!empty($_GET['status'])) {
$where[] = 'status_jalan = ?';
$params[] = $_GET['status'];
$types .= 's';
}
if (!empty($_GET['min_panjang'])) {
$where[] = 'panjang_meter >= ?';
$params[] = (float)$_GET['min_panjang'];
$types .= 'd';
}
if (!empty($_GET['max_panjang'])) {
$where[] = 'panjang_meter <= ?';
$params[] = (float)$_GET['max_panjang'];
$types .= 'd';
}
if (!empty($_GET['q'])) {
$where[] = 'nama_jalan LIKE ?';
$params[] = '%' . $_GET['q'] . '%';
$types .= 's';
}
$sql = 'SELECT id, nama_jalan, status_jalan, panjang_meter, keterangan, geom, created_at FROM data_jalan WHERE ' . implode(' AND ', $where) . ' ORDER BY created_at DESC';
$stmt = $conn->prepare($sql);
if ($params) $stmt->bind_param($types, ...$params);
$stmt->execute();
$rows = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
$data = array_map(function($row) {
$row['koordinat'] = parseGeoJsonToLatLng($row['geom']);
unset($row['geom']);
return $row;
}, $rows);
echo json_encode(['status'=>'success','data'=>$data,'total'=>count($data)]);
}
break;
case 'POST':
$input = getJsonInput();
if (!$input) { http_response_code(400); echo json_encode(['status'=>'error','message'=>'Input tidak valid']); break; }
$nama = trim($input['nama_jalan'] ?? '');
$status = trim($input['status_jalan'] ?? '');
$panjang = (float)($input['panjang_meter'] ?? 0);
$ket = trim($input['keterangan'] ?? '');
$coords = $input['koordinat'] ?? [];
if (!$nama || !$status || count($coords) < 2) {
http_response_code(400);
echo json_encode(['status'=>'error','message'=>'Nama, status, dan minimal 2 koordinat wajib diisi']);
break;
}
if (!in_array($status, ['Nasional','Provinsi','Kabupaten'])) {
http_response_code(400); echo json_encode(['status'=>'error','message'=>'Status tidak valid']); break;
}
$geom = buildGeoJsonLine($coords);
$stmt = $conn->prepare("INSERT INTO data_jalan (nama_jalan,status_jalan,panjang_meter,keterangan,geom) VALUES (?,?,?,?,?)");
$stmt->bind_param("ssdss", $nama, $status, $panjang, $ket, $geom);
if ($stmt->execute()) {
echo json_encode(['status'=>'success','message'=>'Data jalan berhasil ditambahkan','id'=>$conn->insert_id]);
} else {
http_response_code(500); echo json_encode(['status'=>'error','message'=>'Gagal menyimpan: '.$conn->error]);
}
break;
case 'PUT':
$input = getJsonInput();
$id = intval($_GET['id'] ?? 0);
if (!$id || !$input) { http_response_code(400); echo json_encode(['status'=>'error','message'=>'ID dan input wajib ada']); break; }
$nama = trim($input['nama_jalan'] ?? '');
$status = trim($input['status_jalan'] ?? '');
$panjang = (float)($input['panjang_meter'] ?? 0);
$ket = trim($input['keterangan'] ?? '');
$coords = $input['koordinat'] ?? [];
if (!$nama || !$status) { http_response_code(400); echo json_encode(['status'=>'error','message'=>'Nama dan status wajib diisi']); break; }
$geom = buildGeoJsonLine($coords);
$stmt = $conn->prepare("UPDATE data_jalan SET nama_jalan=?,status_jalan=?,panjang_meter=?,keterangan=?,geom=? WHERE id=?");
$stmt->bind_param("ssdssi", $nama, $status, $panjang, $ket, $geom, $id);
if ($stmt->execute()) {
echo json_encode(['status'=>'success','message'=>'Data jalan berhasil diperbarui']);
} else {
http_response_code(500); echo json_encode(['status'=>'error','message'=>'Gagal memperbarui: '.$conn->error]);
}
break;
case 'DELETE':
$id = intval($_GET['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['status'=>'error','message'=>'ID tidak valid']); break; }
$stmt = $conn->prepare("DELETE FROM data_jalan WHERE id=?");
$stmt->bind_param("i", $id);
if ($stmt->execute() && $stmt->affected_rows > 0) {
echo json_encode(['status'=>'success','message'=>'Data jalan berhasil dihapus']);
} else {
http_response_code(404); echo json_encode(['status'=>'error','message'=>'Data tidak ditemukan']);
}
break;
default:
http_response_code(405);
echo json_encode(['status'=>'error','message'=>'Method tidak diizinkan']);
}
$conn->close();

363
jalan_tanah/api/laporan.php Normal file
View File

@@ -0,0 +1,363 @@
<?php
// api/laporan.php — CRUD + Analitik Laporan Jalan Rusak
require_once '../config/database.php';
setCorsHeaders();
$method = $_SERVER['REQUEST_METHOD'];
$conn = getConnection();
$action = $_GET['action'] ?? '';
// ---- Endpoint analitik terpisah ----
if ($method === 'GET' && $action === 'analytics') {
handleAnalytics($conn);
$conn->close();
exit;
}
switch ($method) {
// ==========================================
// GET — list dengan filter
// ==========================================
case 'GET':
if (isset($_GET['id'])) {
$id = intval($_GET['id']);
$stmt = $conn->prepare("SELECT * FROM laporan_jalan_rusak WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
if (!$row) { http_response_code(404); echo json_encode(['status'=>'error','message'=>'Tidak ditemukan']); break; }
$row = formatLaporanRow($row);
echo json_encode(['status'=>'success','data'=>$row]);
} else {
$where = ['1=1'];
$params = [];
$types = '';
// Filter: bulan terakhir (default), atau rentang tahun
if (!empty($_GET['bulan_terakhir'])) {
$bulan = intval($_GET['bulan_terakhir']);
$where[] = 'tanggal_input >= DATE_SUB(NOW(), INTERVAL ? MONTH)';
$params[] = $bulan;
$types .= 'i';
} elseif (!empty($_GET['tahun_terakhir'])) {
$tahun = intval($_GET['tahun_terakhir']);
$where[] = 'tanggal_input >= DATE_SUB(NOW(), INTERVAL ? YEAR)';
$params[] = $tahun;
$types .= 'i';
}
if (!empty($_GET['status'])) {
$where[] = 'status = ?';
$params[] = $_GET['status'];
$types .= 's';
}
if (!empty($_GET['nama_jalan'])) {
$where[] = 'nama_jalan LIKE ?';
$params[] = '%'.$_GET['nama_jalan'].'%';
$types .= 's';
}
if (!empty($_GET['q'])) {
$where[] = '(nama_jalan LIKE ? OR deskripsi LIKE ? OR nama_pelapor LIKE ?)';
$params[] = '%'.$_GET['q'].'%';
$params[] = '%'.$_GET['q'].'%';
$params[] = '%'.$_GET['q'].'%';
$types .= 'sss';
}
$sql = 'SELECT * FROM laporan_jalan_rusak WHERE ' . implode(' AND ', $where) . ' ORDER BY tanggal_input DESC';
$stmt = $conn->prepare($sql);
if ($params) $stmt->bind_param($types, ...$params);
$stmt->execute();
$rows = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
$data = array_map('formatLaporanRow', $rows);
// Hitung cluster (radius 50m, min 3 laporan → urgent)
$clusters = buildClusters($data, 50, 3);
echo json_encode(['status'=>'success','data'=>$data,'total'=>count($data),'clusters'=>$clusters]);
}
break;
// ==========================================
// POST — Tambah laporan (multipart/form-data)
// ==========================================
case 'POST':
$nama_jalan = trim($_POST['nama_jalan'] ?? '');
$deskripsi = trim($_POST['deskripsi'] ?? '');
$nama_pelapor= trim($_POST['nama_pelapor']?? '');
$lat = (float)($_POST['lat'] ?? 0);
$lng = (float)($_POST['lng'] ?? 0);
if (!$nama_jalan || !$lat || !$lng) {
http_response_code(400);
echo json_encode(['status'=>'error','message'=>'Nama jalan dan koordinat wajib diisi']);
break;
}
$foto_path = null;
$foto_lat = null;
$foto_lng = null;
$foto_datetime = null;
// Handle upload foto
if (!empty($_FILES['foto']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['foto']['tmp_name']);
finfo_close($finfo);
if (!in_array($mime, ALLOWED_TYPES)) {
http_response_code(400);
echo json_encode(['status'=>'error','message'=>'Format foto tidak didukung (JPG/PNG/WebP)']);
break;
}
if ($_FILES['foto']['size'] > MAX_FILE_SIZE) {
http_response_code(400);
echo json_encode(['status'=>'error','message'=>'Ukuran foto melebihi batas 8MB']);
break;
}
// Baca EXIF sebelum pindah file
$exifData = @exif_read_data($_FILES['foto']['tmp_name']);
if ($exifData) {
// Ekstrak koordinat GPS dari EXIF
$gpsCoords = extractExifGps($exifData);
if ($gpsCoords) {
$foto_lat = $gpsCoords['lat'];
$foto_lng = $gpsCoords['lng'];
}
// Ekstrak datetime
$dt = $exifData['DateTimeOriginal'] ?? $exifData['DateTime'] ?? null;
if ($dt) {
$foto_datetime = date('Y-m-d H:i:s', strtotime(str_replace(':', '-', substr($dt, 0, 10)) . substr($dt, 10)));
}
}
// Jika GPS tidak ada di EXIF, pakai koordinat yang dikirim user
if (!$foto_lat) $foto_lat = $lat;
if (!$foto_lng) $foto_lng = $lng;
if (!is_dir(UPLOAD_DIR)) mkdir(UPLOAD_DIR, 0755, true);
$ext = match($mime) { 'image/png' => 'png', 'image/webp' => 'webp', default => 'jpg' };
$filename = 'laporan_' . date('Ymd_His') . '_' . uniqid() . '.' . $ext;
$destPath = UPLOAD_DIR . $filename;
if (!move_uploaded_file($_FILES['foto']['tmp_name'], $destPath)) {
http_response_code(500);
echo json_encode(['status'=>'error','message'=>'Gagal menyimpan foto']);
break;
}
$foto_path = UPLOAD_URL . $filename;
}
// Koordinat point: prioritaskan GPS foto, fallback ke input user
$pointLat = $foto_lat ?? $lat;
$pointLng = $foto_lng ?? $lng;
$geom = buildGeoJsonPoint($pointLat, $pointLng);
$stmt = $conn->prepare(
"INSERT INTO laporan_jalan_rusak (nama_pelapor,nama_jalan,deskripsi,foto_path,foto_lat,foto_lng,foto_datetime,geom)
VALUES (?,?,?,?,?,?,?,?)"
);
$npVal = $nama_pelapor ?: null;
$stmt->bind_param("ssssddss", $npVal, $nama_jalan, $deskripsi, $foto_path, $foto_lat, $foto_lng, $foto_datetime, $geom);
if ($stmt->execute()) {
echo json_encode(['status'=>'success','message'=>'Laporan berhasil dikirim','id'=>$conn->insert_id]);
} else {
http_response_code(500);
echo json_encode(['status'=>'error','message'=>'Gagal menyimpan: '.$conn->error]);
}
break;
// ==========================================
// PUT — Update status laporan
// ==========================================
case 'PUT':
$input = getJsonInput();
$id = intval($_GET['id'] ?? 0);
if (!$id || !$input) { http_response_code(400); echo json_encode(['status'=>'error','message'=>'ID dan input wajib ada']); break; }
$status = trim($input['status'] ?? '');
if (!in_array($status, ['pending','verified','resolved'])) {
http_response_code(400); echo json_encode(['status'=>'error','message'=>'Status tidak valid']); break;
}
$stmt = $conn->prepare("UPDATE laporan_jalan_rusak SET status=? WHERE id=?");
$stmt->bind_param("si", $status, $id);
if ($stmt->execute()) {
echo json_encode(['status'=>'success','message'=>'Status laporan diperbarui']);
} else {
http_response_code(500); echo json_encode(['status'=>'error','message'=>$conn->error]);
}
break;
// ==========================================
// DELETE
// ==========================================
case 'DELETE':
$id = intval($_GET['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['status'=>'error','message'=>'ID tidak valid']); break; }
// Hapus file foto juga
$stmt = $conn->prepare("SELECT foto_path FROM laporan_jalan_rusak WHERE id=?");
$stmt->bind_param("i", $id);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
if ($row && $row['foto_path']) {
$fullPath = __DIR__ . '/../' . $row['foto_path'];
if (file_exists($fullPath)) @unlink($fullPath);
}
$stmt = $conn->prepare("DELETE FROM laporan_jalan_rusak WHERE id=?");
$stmt->bind_param("i", $id);
if ($stmt->execute() && $stmt->affected_rows > 0) {
echo json_encode(['status'=>'success','message'=>'Laporan berhasil dihapus']);
} else {
http_response_code(404); echo json_encode(['status'=>'error','message'=>'Data tidak ditemukan']);
}
break;
default:
http_response_code(405);
echo json_encode(['status'=>'error','message'=>'Method tidak diizinkan']);
}
$conn->close();
// ============================================
// HELPERS
// ============================================
function formatLaporanRow(array $row): array {
$geom = json_decode($row['geom'] ?? '{}', true);
$row['lat'] = $geom['coordinates'][1] ?? null;
$row['lng'] = $geom['coordinates'][0] ?? null;
unset($row['geom']);
return $row;
}
function extractExifGps(array $exif): ?array {
if (empty($exif['GPSLatitude']) || empty($exif['GPSLongitude'])) return null;
$lat = gpsToDecimal($exif['GPSLatitude'], $exif['GPSLatitudeRef'] ?? 'N');
$lng = gpsToDecimal($exif['GPSLongitude'], $exif['GPSLongitudeRef'] ?? 'E');
return ['lat' => $lat, 'lng' => $lng];
}
function gpsToDecimal(array $parts, string $ref): float {
$deg = evalFraction($parts[0]);
$min = evalFraction($parts[1]);
$sec = evalFraction($parts[2]);
$dec = $deg + ($min / 60) + ($sec / 3600);
return in_array($ref, ['S','W']) ? -$dec : $dec;
}
function evalFraction(string $frac): float {
if (strpos($frac, '/') === false) return (float)$frac;
[$n, $d] = explode('/', $frac);
return $d ? (float)$n / (float)$d : 0;
}
/**
* Hitung jarak Haversine antara dua titik (meter)
*/
function haversine(float $lat1, float $lng1, float $lat2, float $lng2): float {
$R = 6371000;
$p1 = deg2rad($lat1); $p2 = deg2rad($lat2);
$dp = deg2rad($lat2 - $lat1);
$dl = deg2rad($lng2 - $lng1);
$a = sin($dp/2)**2 + cos($p1)*cos($p2)*sin($dl/2)**2;
return $R * 2 * atan2(sqrt($a), sqrt(1-$a));
}
/**
* Bangun cluster sederhana: greedy radius clustering
* Laporan dalam radius $radiusM meter dianggap satu cluster.
* Cluster dengan >= $minCount laporan ditandai urgent.
*/
function buildClusters(array $laporan, float $radiusM, int $minCount): array {
$clusters = [];
$assigned = [];
foreach ($laporan as $i => $l) {
if (isset($assigned[$i])) continue;
if ($l['lat'] === null) continue;
$cluster = [$i];
$assigned[$i] = true;
foreach ($laporan as $j => $m) {
if ($j === $i || isset($assigned[$j])) continue;
if ($m['lat'] === null) continue;
if (haversine($l['lat'], $l['lng'], $m['lat'], $m['lng']) <= $radiusM) {
$cluster[] = $j;
$assigned[$j] = true;
}
}
if (count($cluster) >= $minCount) {
// Centroid
$cLat = array_sum(array_map(fn($k) => $laporan[$k]['lat'], $cluster)) / count($cluster);
$cLng = array_sum(array_map(fn($k) => $laporan[$k]['lng'], $cluster)) / count($cluster);
$ids = array_map(fn($k) => $laporan[$k]['id'], $cluster);
$jalanNames = array_unique(array_map(fn($k) => $laporan[$k]['nama_jalan'], $cluster));
$clusters[] = [
'lat' => $cLat,
'lng' => $cLng,
'count' => count($cluster),
'ids' => $ids,
'nama_jalan' => implode(', ', $jalanNames),
'urgent' => count($cluster) >= $minCount,
];
}
}
return $clusters;
}
/**
* Analitik: frekuensi kerusakan per jalan, per periode
*/
function handleAnalytics(mysqli $conn): void {
$tahun = intval($_GET['tahun'] ?? 1);
if (!in_array($tahun, [1,2,3,5,10])) $tahun = 1;
// Frekuensi per jalan
$stmt = $conn->prepare(
"SELECT nama_jalan,
COUNT(*) AS total,
SUM(CASE WHEN status='resolved' THEN 1 ELSE 0 END) AS resolved,
SUM(CASE WHEN status='verified' THEN 1 ELSE 0 END) AS verified,
SUM(CASE WHEN status='pending' THEN 1 ELSE 0 END) AS pending,
MIN(tanggal_input) AS pertama,
MAX(tanggal_input) AS terakhir
FROM laporan_jalan_rusak
WHERE tanggal_input >= DATE_SUB(NOW(), INTERVAL ? YEAR)
GROUP BY nama_jalan
ORDER BY total DESC"
);
$stmt->bind_param("i", $tahun);
$stmt->execute();
$perJalan = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
// Tren per bulan (12 bulan terakhir)
$tren = $conn->query(
"SELECT DATE_FORMAT(tanggal_input,'%Y-%m') AS bulan, COUNT(*) AS total
FROM laporan_jalan_rusak
WHERE tanggal_input >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY bulan ORDER BY bulan ASC"
)->fetch_all(MYSQLI_ASSOC);
// Sering rusak: jalan dengan total >= 3 dalam rentang waktu
$seringRusak = array_filter($perJalan, fn($r) => (int)$r['total'] >= 3);
echo json_encode([
'status' => 'success',
'per_jalan' => $perJalan,
'tren_bulanan' => $tren,
'sering_rusak' => array_values($seringRusak),
'rentang_tahun'=> $tahun,
]);
}

144
jalan_tanah/api/parsil.php Normal file
View File

@@ -0,0 +1,144 @@
<?php
// api/parsil.php — CRUD Data Parsil Tanah (GeoJSON Polygon)
require_once '../config/database.php';
setCorsHeaders();
$method = $_SERVER['REQUEST_METHOD'];
$conn = getConnection();
switch ($method) {
case 'GET':
if (isset($_GET['id'])) {
$id = intval($_GET['id']);
$stmt = $conn->prepare("SELECT id, nama_parsil, pemilik, status_sertifikat, nomor_sertifikat, luas_meter2, keterangan, geom, created_at FROM data_parsil WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
if (!$row) { http_response_code(404); echo json_encode(['status'=>'error','message'=>'Data tidak ditemukan']); break; }
$row['koordinat'] = parseGeoJsonToLatLng($row['geom']);
unset($row['geom']);
echo json_encode(['status'=>'success','data'=>$row]);
} else {
$where = ['1=1'];
$params = [];
$types = '';
if (!empty($_GET['status'])) {
$where[] = 'status_sertifikat = ?';
$params[] = $_GET['status'];
$types .= 's';
}
if (!empty($_GET['min_luas'])) {
$where[] = 'luas_meter2 >= ?';
$params[] = (float)$_GET['min_luas'];
$types .= 'd';
}
if (!empty($_GET['max_luas'])) {
$where[] = 'luas_meter2 <= ?';
$params[] = (float)$_GET['max_luas'];
$types .= 'd';
}
if (!empty($_GET['q'])) {
$where[] = '(nama_parsil LIKE ? OR pemilik LIKE ?)';
$params[] = '%'.$_GET['q'].'%';
$params[] = '%'.$_GET['q'].'%';
$types .= 'ss';
}
$sql = 'SELECT id, nama_parsil, pemilik, status_sertifikat, nomor_sertifikat, luas_meter2, keterangan, geom, created_at FROM data_parsil WHERE ' . implode(' AND ', $where) . ' ORDER BY created_at DESC';
$stmt = $conn->prepare($sql);
if ($params) $stmt->bind_param($types, ...$params);
$stmt->execute();
$rows = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
$data = array_map(function($row) {
$row['koordinat'] = parseGeoJsonToLatLng($row['geom']);
unset($row['geom']);
return $row;
}, $rows);
echo json_encode(['status'=>'success','data'=>$data,'total'=>count($data)]);
}
break;
case 'POST':
$input = getJsonInput();
if (!$input) { http_response_code(400); echo json_encode(['status'=>'error','message'=>'Input tidak valid']); break; }
$nama = trim($input['nama_parsil'] ?? '');
$pemilik = trim($input['pemilik'] ?? '');
$status = trim($input['status_sertifikat'] ?? '');
$nosert = trim($input['nomor_sertifikat'] ?? '');
$luas = (float)($input['luas_meter2'] ?? 0);
$ket = trim($input['keterangan'] ?? '');
$coords = $input['koordinat'] ?? [];
if (!$nama || !$pemilik || !$status || count($coords) < 3) {
http_response_code(400);
echo json_encode(['status'=>'error','message'=>'Nama, pemilik, status, dan minimal 3 koordinat wajib diisi']);
break;
}
if (!in_array($status, ['SHM','HGB','HGU','HP'])) {
http_response_code(400); echo json_encode(['status'=>'error','message'=>'Status tidak valid']); break;
}
$geom = buildGeoJsonPolygon($coords);
$stmt = $conn->prepare("INSERT INTO data_parsil (nama_parsil,pemilik,status_sertifikat,nomor_sertifikat,luas_meter2,keterangan,geom) VALUES (?,?,?,?,?,?,?)");
$stmt->bind_param("ssssdss", $nama, $pemilik, $status, $nosert, $luas, $ket, $geom);
if ($stmt->execute()) {
echo json_encode(['status'=>'success','message'=>'Data parsil berhasil ditambahkan','id'=>$conn->insert_id]);
} else {
http_response_code(500); echo json_encode(['status'=>'error','message'=>'Gagal menyimpan: '.$conn->error]);
}
break;
case 'PUT':
$input = getJsonInput();
$id = intval($_GET['id'] ?? 0);
if (!$id || !$input) { http_response_code(400); echo json_encode(['status'=>'error','message'=>'ID dan input wajib ada']); break; }
$nama = trim($input['nama_parsil'] ?? '');
$pemilik = trim($input['pemilik'] ?? '');
$status = trim($input['status_sertifikat'] ?? '');
$nosert = trim($input['nomor_sertifikat'] ?? '');
$luas = (float)($input['luas_meter2'] ?? 0);
$ket = trim($input['keterangan'] ?? '');
$coords = $input['koordinat'] ?? [];
if (!$nama || !$pemilik || !$status) {
http_response_code(400); echo json_encode(['status'=>'error','message'=>'Nama, pemilik, dan status wajib diisi']); break;
}
$geom = buildGeoJsonPolygon($coords);
$stmt = $conn->prepare("UPDATE data_parsil SET nama_parsil=?,pemilik=?,status_sertifikat=?,nomor_sertifikat=?,luas_meter2=?,keterangan=?,geom=? WHERE id=?");
$stmt->bind_param("ssssdssi", $nama, $pemilik, $status, $nosert, $luas, $ket, $geom, $id);
if ($stmt->execute()) {
echo json_encode(['status'=>'success','message'=>'Data parsil berhasil diperbarui']);
} else {
http_response_code(500); echo json_encode(['status'=>'error','message'=>'Gagal memperbarui: '.$conn->error]);
}
break;
case 'DELETE':
$id = intval($_GET['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['status'=>'error','message'=>'ID tidak valid']); break; }
$stmt = $conn->prepare("DELETE FROM data_parsil WHERE id=?");
$stmt->bind_param("i", $id);
if ($stmt->execute() && $stmt->affected_rows > 0) {
echo json_encode(['status'=>'success','message'=>'Data parsil berhasil dihapus']);
} else {
http_response_code(404); echo json_encode(['status'=>'error','message'=>'Data tidak ditemukan']);
}
break;
default:
http_response_code(405);
echo json_encode(['status'=>'error','message'=>'Method tidak diizinkan']);
}
$conn->close();