Initial commit
This commit is contained in:
138
jalan_tanah/api/jalan.php
Normal file
138
jalan_tanah/api/jalan.php
Normal 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
363
jalan_tanah/api/laporan.php
Normal 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
144
jalan_tanah/api/parsil.php
Normal 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();
|
||||
Reference in New Issue
Block a user