initial commit
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
# 🗺️ WebGIS Manajemen Jalan & Parsil Tanah
|
||||
|
||||
Sistem Informasi Geografis berbasis web untuk manajemen data spasial jalan dan parsil tanah.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Struktur File
|
||||
|
||||
```
|
||||
webgis/
|
||||
├── index.html ← Halaman utama WebGIS
|
||||
├── database.sql ← Schema & sample data MySQL
|
||||
├── README.md ← Dokumentasi ini
|
||||
└── api/
|
||||
├── config.php ← Konfigurasi koneksi database
|
||||
├── jalan.php ← API CRUD Data Jalan
|
||||
└── parsil.php ← API CRUD Data Parsil Tanah
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Cara Install
|
||||
|
||||
### 1. Persyaratan
|
||||
- **Web Server**: Apache / Nginx dengan PHP 7.4+
|
||||
- **Database**: MySQL 5.7+ atau MariaDB 10.3+
|
||||
- **Browser**: Chrome, Firefox, Edge (modern)
|
||||
|
||||
### 2. Setup Database
|
||||
|
||||
```sql
|
||||
-- Jalankan di MySQL / phpMyAdmin
|
||||
SOURCE database.sql;
|
||||
```
|
||||
|
||||
Atau import file `database.sql` melalui phpMyAdmin.
|
||||
|
||||
### 3. Konfigurasi Database
|
||||
|
||||
Edit file `api/config.php`:
|
||||
|
||||
```php
|
||||
define('DB_HOST', 'localhost'); // Host database
|
||||
define('DB_USER', 'root'); // Username MySQL
|
||||
define('DB_PASS', ''); // Password MySQL
|
||||
define('DB_NAME', 'webgis_db'); // Nama database
|
||||
```
|
||||
|
||||
### 4. Deploy ke Web Server
|
||||
|
||||
**Menggunakan XAMPP/WAMP/LAMP:**
|
||||
```bash
|
||||
# Copy folder webgis/ ke:
|
||||
# Windows XAMPP: C:/xampp/htdocs/webgis/
|
||||
# Linux LAMP: /var/www/html/webgis/
|
||||
```
|
||||
|
||||
**Akses aplikasi:**
|
||||
```
|
||||
http://localhost/polyline_polygon/index.html
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Fitur Utama
|
||||
|
||||
### 🛣️ Manajemen Data Jalan (Polyline)
|
||||
| Fitur | Deskripsi |
|
||||
|-------|-----------|
|
||||
| **Tambah Jalan** | Gambar polyline di peta atau input koordinat manual |
|
||||
| **Status Jalan** | Nasional (merah), Provinsi (oranye), Kabupaten (hijau) |
|
||||
| **Panjang Otomatis** | Dihitung menggunakan `L.latLng.distanceTo()` dari LeafletJS |
|
||||
| **Edit & Hapus** | CRUD lengkap dengan konfirmasi hapus |
|
||||
| **Drag Titik** | Titik koordinat bisa diubah langsung di tabel |
|
||||
|
||||
### 🏘️ Manajemen Parsil Tanah (Polygon)
|
||||
| Fitur | Deskripsi |
|
||||
|-------|-----------|
|
||||
| **Tambah Parsil** | Gambar polygon di peta atau input koordinat manual |
|
||||
| **Status Kepemilikan** | SHM, HGB, HGU, HP — masing-masing warna berbeda |
|
||||
| **Luas Otomatis** | Dihitung dengan formula Spherical Excess dari LeafletJS |
|
||||
| **Data Lengkap** | Nama parsil, nomor sertifikat, nama pemilik, keterangan |
|
||||
|
||||
### 🗺️ Fitur Peta
|
||||
- **Basemap**: OpenStreetMap + Esri Satellite (switchable)
|
||||
- **Draw Mode**: Klik peta untuk menggambar fitur spasial
|
||||
- **Popup Interaktif**: Klik fitur di peta untuk melihat detail
|
||||
- **Zoom to Feature**: Langsung zoom ke lokasi dari sidebar
|
||||
- **Koordinat Real-time**: Tampil di pojok kiri bawah
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Kode Warna
|
||||
|
||||
### Jalan
|
||||
| Status | Warna | Hex |
|
||||
|--------|-------|-----|
|
||||
| Jalan Nasional | 🔴 Merah | `#E63946` |
|
||||
| Jalan Provinsi | 🟠 Oranye | `#F4A261` |
|
||||
| Jalan Kabupaten | 🟢 Hijau | `#2A9D8F` |
|
||||
|
||||
### Parsil Tanah
|
||||
| Status | Warna | Hex |
|
||||
|--------|-------|-----|
|
||||
| SHM | 🔵 Biru | `#4361EE` |
|
||||
| HGB | 🟣 Ungu | `#7209B7` |
|
||||
| HGU | 🌸 Pink | `#F72585` |
|
||||
| HP | 🩵 Cyan | `#4CC9F0` |
|
||||
|
||||
---
|
||||
|
||||
## 📡 API Endpoints
|
||||
|
||||
### Jalan
|
||||
```
|
||||
GET api/jalan.php → Ambil semua data jalan
|
||||
GET api/jalan.php?id={id} → Ambil data jalan by ID
|
||||
POST api/jalan.php → Tambah data jalan baru
|
||||
PUT api/jalan.php?id={id} → Update data jalan
|
||||
DELETE api/jalan.php?id={id} → Hapus data jalan
|
||||
```
|
||||
|
||||
### Parsil Tanah
|
||||
```
|
||||
GET api/parsil.php → Ambil semua data parsil
|
||||
GET api/parsil.php?id={id} → Ambil data parsil by ID
|
||||
POST api/parsil.php → Tambah data parsil baru
|
||||
PUT api/parsil.php?id={id} → Update data parsil
|
||||
DELETE api/parsil.php?id={id} → Hapus data parsil
|
||||
```
|
||||
|
||||
### Contoh Request Body (POST/PUT Jalan)
|
||||
```json
|
||||
{
|
||||
"nama_jalan": "Jalan Ahmad Yani",
|
||||
"status_jalan": "Jalan Nasional",
|
||||
"panjang_meter": 1250.5,
|
||||
"koordinat": [
|
||||
[0.0032, -109.3245],
|
||||
[0.0051, -109.3180],
|
||||
[0.0068, -109.3110]
|
||||
],
|
||||
"keterangan": "Jalan utama kota"
|
||||
}
|
||||
```
|
||||
|
||||
### Contoh Request Body (POST/PUT Parsil)
|
||||
```json
|
||||
{
|
||||
"nama_parsil": "Kavling A-001",
|
||||
"nomor_sertifikat": "SHM-2024-001",
|
||||
"status_kepemilikan": "SHM",
|
||||
"luas_meter2": 450.0,
|
||||
"koordinat": [
|
||||
[0.0010, -109.3280],
|
||||
[0.0010, -109.3260],
|
||||
[0.0020, -109.3260],
|
||||
[0.0020, -109.3280]
|
||||
],
|
||||
"nama_pemilik": "Budi Santoso",
|
||||
"keterangan": "Kavling perumahan"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Cara Penggunaan
|
||||
|
||||
### Menambah Data Jalan Baru
|
||||
1. Klik tab **"Data Jalan"** di topbar
|
||||
2. Klik **"✚ Tambah Data Baru"** di sidebar
|
||||
3. **Opsi A**: Klik **"✏️ Gambar di Peta"** → klik titik-titik di peta → klik **"⏹ Selesai Gambar"**
|
||||
4. **Opsi B**: Isi koordinat manual di tabel (lat/lng double)
|
||||
5. Isi nama jalan, pilih status, tambah keterangan
|
||||
6. Panjang jalan otomatis terhitung oleh LeafletJS
|
||||
7. Klik **"💾 Simpan Data"**
|
||||
|
||||
### Menambah Data Parsil Baru
|
||||
1. Klik tab **"Parsil Tanah"** di topbar
|
||||
2. Klik **"✚ Tambah Data Baru"**
|
||||
3. Gambar polygon di peta (min. 3 titik) atau isi koordinat manual
|
||||
4. Luas tanah otomatis terhitung oleh LeafletJS
|
||||
5. Isi nama parsil, nomor sertifikat, status kepemilikan, nama pemilik
|
||||
6. Klik **"💾 Simpan Data"**
|
||||
|
||||
### Edit Data
|
||||
1. Klik **"✏️ Edit"** pada kartu data di sidebar, atau klik fitur di peta → klik Edit di popup
|
||||
2. Ubah data yang diperlukan, termasuk koordinat
|
||||
3. Klik **"💾 Simpan Data"**
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Mode Demo
|
||||
|
||||
Jika server PHP belum terkonfigurasi, aplikasi otomatis berjalan dalam **Mode Demo** dengan data sample. Semua operasi CRUD tetap berfungsi secara lokal di browser (tidak tersimpan ke database).
|
||||
|
||||
---
|
||||
|
||||
## 📚 Teknologi
|
||||
|
||||
- **Frontend**: HTML5, CSS3 (Custom Properties), Vanilla JavaScript
|
||||
- **Peta**: LeafletJS 1.9.4 + Leaflet.draw 1.0.4
|
||||
- **Backend**: PHP 7.4+ (REST API)
|
||||
- **Database**: MySQL / MariaDB
|
||||
- **Basemap**: OpenStreetMap, Esri World Imagery
|
||||
- **Font**: Plus Jakarta Sans, DM Mono (Google Fonts)
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
// config.php - Konfigurasi Database
|
||||
define('DB_HOST', 'localhost');
|
||||
define('DB_USER', 'root');
|
||||
define('DB_PASS', '');
|
||||
define('DB_NAME', 'polyline_polygon');
|
||||
|
||||
function getDB() {
|
||||
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
|
||||
if ($conn->connect_error) {
|
||||
die(json_encode(['success' => false, 'message' => 'Koneksi database gagal: ' . $conn->connect_error]));
|
||||
}
|
||||
$conn->set_charset('utf8mb4');
|
||||
return $conn;
|
||||
}
|
||||
|
||||
// Set header untuk API
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
exit(0);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
// api/jalan.php - API CRUD untuk Data Jalan (Format GeoJSON)
|
||||
require_once 'config.php';
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
switch ($method) {
|
||||
case 'GET': getJalan($id); break;
|
||||
case 'POST': createJalan(); break;
|
||||
case 'PUT': updateJalan($id); break;
|
||||
case 'DELETE': deleteJalan($id); break;
|
||||
default:
|
||||
echo json_encode(['success' => false, 'message' => 'Method tidak diizinkan']);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Helper: baris DB → array siap kirim ke client
|
||||
// Mengembalikan struktur flat yang dipakai frontend,
|
||||
// dengan field `koordinat` berisi [[lat,lng],...] agar
|
||||
// Leaflet langsung bisa dipakai, PLUS field `geojson`
|
||||
// berisi GeoJSON Feature asli.
|
||||
// ─────────────────────────────────────────────
|
||||
function rowToResponse($row) {
|
||||
$feature = json_decode($row['geojson'], true);
|
||||
|
||||
// GeoJSON coords = [lng, lat] → ubah ke [lat, lng] untuk Leaflet
|
||||
$coords_geojson = $feature['geometry']['coordinates'] ?? [];
|
||||
$koordinat_leaflet = array_map(fn($c) => [$c[1], $c[0]], $coords_geojson);
|
||||
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'nama_jalan' => $row['nama_jalan'],
|
||||
'status_jalan' => $row['status_jalan'],
|
||||
'panjang_meter' => (float)$row['panjang_meter'],
|
||||
'koordinat' => $koordinat_leaflet, // [lat,lng] untuk Leaflet
|
||||
'geojson' => $feature, // GeoJSON Feature asli
|
||||
'created_at' => $row['created_at'],
|
||||
'updated_at' => $row['updated_at'],
|
||||
];
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Helper: koordinat [[lat,lng],...] → GeoJSON Feature string
|
||||
// ─────────────────────────────────────────────
|
||||
function buildGeoJSON($nama, $status, $panjang, $koordinat_leaflet) {
|
||||
// Leaflet: [lat, lng] → GeoJSON: [lng, lat]
|
||||
$coords = array_map(fn($c) => [(float)$c[1], (float)$c[0]], $koordinat_leaflet);
|
||||
|
||||
$feature = [
|
||||
'type' => 'Feature',
|
||||
'properties' => [
|
||||
'nama_jalan' => $nama,
|
||||
'status_jalan' => $status,
|
||||
'panjang_meter' => (float)$panjang,
|
||||
],
|
||||
'geometry' => [
|
||||
'type' => 'LineString',
|
||||
'coordinates' => $coords,
|
||||
],
|
||||
];
|
||||
return json_encode($feature, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
function getJalan($id = null) {
|
||||
$db = getDB();
|
||||
if ($id) {
|
||||
$stmt = $db->prepare("SELECT * FROM jalan WHERE id = ?");
|
||||
$stmt->bind_param('i', $id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$row = $result->fetch_assoc();
|
||||
if ($row) {
|
||||
echo json_encode(['success' => true, 'data' => rowToResponse($row)]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Data tidak ditemukan']);
|
||||
}
|
||||
} else {
|
||||
$result = $db->query("SELECT * FROM jalan ORDER BY created_at DESC");
|
||||
$rows = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$rows[] = rowToResponse($row);
|
||||
}
|
||||
echo json_encode(['success' => true, 'data' => $rows, 'total' => count($rows)]);
|
||||
}
|
||||
$db->close();
|
||||
}
|
||||
|
||||
function createJalan() {
|
||||
$db = getDB();
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (empty($input['nama_jalan']) || empty($input['status_jalan']) || empty($input['koordinat'])) {
|
||||
echo json_encode(['success' => false, 'message' => 'Data tidak lengkap']);
|
||||
return;
|
||||
}
|
||||
|
||||
$nama = $db->real_escape_string($input['nama_jalan']);
|
||||
$status = $db->real_escape_string($input['status_jalan']);
|
||||
$panjang = (float)($input['panjang_meter'] ?? 0);
|
||||
$geojson = buildGeoJSON($nama, $status, $panjang, $input['koordinat']);
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO jalan (nama_jalan, status_jalan, panjang_meter, geojson) VALUES (?, ?, ?, ?)");
|
||||
$stmt->bind_param('ssds', $nama, $status, $panjang, $geojson);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'message' => 'Data jalan berhasil disimpan', 'id' => $db->insert_id]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal menyimpan data: ' . $db->error]);
|
||||
}
|
||||
$db->close();
|
||||
}
|
||||
|
||||
function updateJalan($id) {
|
||||
if (!$id) { echo json_encode(['success' => false, 'message' => 'ID diperlukan']); return; }
|
||||
$db = getDB();
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$nama = $db->real_escape_string($input['nama_jalan']);
|
||||
$status = $db->real_escape_string($input['status_jalan']);
|
||||
$panjang = (float)($input['panjang_meter'] ?? 0);
|
||||
$geojson = buildGeoJSON($nama, $status, $panjang, $input['koordinat']);
|
||||
|
||||
$stmt = $db->prepare("UPDATE jalan SET nama_jalan=?, status_jalan=?, panjang_meter=?, geojson=?, updated_at=NOW() WHERE id=?");
|
||||
$stmt->bind_param('ssdsi', $nama, $status, $panjang, $geojson, $id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'message' => 'Data jalan berhasil diperbarui']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal memperbarui data: ' . $db->error]);
|
||||
}
|
||||
$db->close();
|
||||
}
|
||||
|
||||
function deleteJalan($id) {
|
||||
if (!$id) { echo json_encode(['success' => false, 'message' => 'ID diperlukan']); return; }
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("DELETE FROM jalan WHERE id = ?");
|
||||
$stmt->bind_param('i', $id);
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'message' => 'Data jalan berhasil dihapus']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal menghapus data']);
|
||||
}
|
||||
$db->close();
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
// api/laporan.php - API CRUD untuk Laporan Kerusakan Jalan (Format GeoJSON Point)
|
||||
require_once 'config.php';
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
switch ($method) {
|
||||
case 'GET': getLaporan($id); break;
|
||||
case 'POST': createLaporan(); break;
|
||||
case 'PUT': updateLaporan($id); break;
|
||||
case 'DELETE': deleteLaporan($id); break;
|
||||
default:
|
||||
echo json_encode(['success' => false, 'message' => 'Method tidak diizinkan']);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Info anggaran berdasarkan status jalan
|
||||
// ─────────────────────────────────────────────
|
||||
function getInfoAnggaran($status_jalan) {
|
||||
switch ($status_jalan) {
|
||||
case 'Jalan Nasional':
|
||||
return ['pengelola' => 'Kementerian PUPR', 'sumber' => 'APBN'];
|
||||
case 'Jalan Provinsi':
|
||||
return ['pengelola' => 'Dinas PUPR Provinsi', 'sumber' => 'APBD Provinsi'];
|
||||
case 'Jalan Kabupaten':
|
||||
return ['pengelola' => 'Dinas PUPR Kabupaten/Kota', 'sumber' => 'APBD Kabupaten'];
|
||||
default:
|
||||
return ['pengelola' => 'Tidak Diketahui', 'sumber' => '-'];
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Helper: baris DB → array siap kirim ke client
|
||||
// ─────────────────────────────────────────────
|
||||
function rowToResponse($row) {
|
||||
$feature = json_decode($row['geojson'], true);
|
||||
$coords_geojson = $feature['geometry']['coordinates'] ?? [0, 0];
|
||||
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'jalan_id' => (int)$row['jalan_id'],
|
||||
'nama_jalan' => $row['nama_jalan'],
|
||||
'status_jalan' => $row['status_jalan'],
|
||||
'info_anggaran' => getInfoAnggaran($row['status_jalan']),
|
||||
'nama_pelapor' => $row['nama_pelapor'],
|
||||
'kategori_rusak' => $row['kategori_rusak'],
|
||||
'tanggal_laporan' => $row['tanggal_laporan'],
|
||||
'keterangan' => $row['keterangan'],
|
||||
'foto_url' => $row['foto_url'],
|
||||
'lat' => $coords_geojson[1],
|
||||
'lng' => $coords_geojson[0],
|
||||
'geojson' => $feature,
|
||||
'created_at' => $row['created_at'],
|
||||
'updated_at' => $row['updated_at'],
|
||||
];
|
||||
}
|
||||
|
||||
function getLaporan($id = null) {
|
||||
$db = getDB();
|
||||
|
||||
// Query parameter opsional: filter bulan, tahun, jalan_id
|
||||
$where = ['1=1'];
|
||||
$params = [];
|
||||
$types = '';
|
||||
|
||||
if (isset($_GET['bulan_terakhir'])) {
|
||||
$n = (int)$_GET['bulan_terakhir'];
|
||||
$where[] = "tanggal_laporan >= DATE_SUB(CURDATE(), INTERVAL ? MONTH)";
|
||||
$params[] = $n;
|
||||
$types .= 'i';
|
||||
}
|
||||
if (isset($_GET['tahun_terakhir'])) {
|
||||
$n = (int)$_GET['tahun_terakhir'];
|
||||
$where[] = "tanggal_laporan >= DATE_SUB(CURDATE(), INTERVAL ? YEAR)";
|
||||
$params[] = $n;
|
||||
$types .= 'i';
|
||||
}
|
||||
if (isset($_GET['jalan_id'])) {
|
||||
$where[] = "jalan_id = ?";
|
||||
$params[] = (int)$_GET['jalan_id'];
|
||||
$types .= 'i';
|
||||
}
|
||||
if (isset($_GET['kategori'])) {
|
||||
$where[] = "kategori_rusak = ?";
|
||||
$params[] = $_GET['kategori'];
|
||||
$types .= 's';
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
$stmt = $db->prepare("SELECT * FROM laporan_kerusakan WHERE id = ?");
|
||||
$stmt->bind_param('i', $id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$row = $result->fetch_assoc();
|
||||
if ($row) {
|
||||
echo json_encode(['success' => true, 'data' => rowToResponse($row)]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Data tidak ditemukan']);
|
||||
}
|
||||
} else {
|
||||
$sql = "SELECT * FROM laporan_kerusakan WHERE " . implode(' AND ', $where) . " ORDER BY tanggal_laporan DESC";
|
||||
$stmt = $db->prepare($sql);
|
||||
if ($params) {
|
||||
$stmt->bind_param($types, ...$params);
|
||||
}
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$rows = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$rows[] = rowToResponse($row);
|
||||
}
|
||||
|
||||
// Hitung jalan yang sering rusak (muncul >= 3 laporan dalam 3 tahun)
|
||||
$res_sering = $db->query("
|
||||
SELECT jalan_id, nama_jalan, COUNT(*) as jumlah
|
||||
FROM laporan_kerusakan
|
||||
WHERE tanggal_laporan >= DATE_SUB(CURDATE(), INTERVAL 3 YEAR)
|
||||
GROUP BY jalan_id, nama_jalan
|
||||
HAVING jumlah >= 3
|
||||
ORDER BY jumlah DESC
|
||||
");
|
||||
$sering_rusak = [];
|
||||
while ($sr = $res_sering->fetch_assoc()) {
|
||||
$sering_rusak[] = ['jalan_id' => (int)$sr['jalan_id'], 'nama_jalan' => $sr['nama_jalan'], 'jumlah_laporan' => (int)$sr['jumlah']];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $rows,
|
||||
'total' => count($rows),
|
||||
'sering_rusak' => $sering_rusak
|
||||
]);
|
||||
}
|
||||
$db->close();
|
||||
}
|
||||
|
||||
function createLaporan() {
|
||||
$db = getDB();
|
||||
|
||||
// Cek apakah ada upload foto
|
||||
$foto_url = null;
|
||||
if (!empty($_FILES['foto']['tmp_name'])) {
|
||||
$ext = pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION);
|
||||
$filename = 'laporan_' . time() . '_' . rand(1000,9999) . '.' . strtolower($ext);
|
||||
$upload_dir = '../uploads/laporan/';
|
||||
if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
|
||||
if (move_uploaded_file($_FILES['foto']['tmp_name'], $upload_dir . $filename)) {
|
||||
$foto_url = 'uploads/laporan/' . $filename;
|
||||
}
|
||||
}
|
||||
|
||||
// Baca JSON dari body (jika bukan multipart)
|
||||
$input = [];
|
||||
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
|
||||
if (strpos($contentType, 'application/json') !== false) {
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
// Foto bisa base64 embedded
|
||||
if (!empty($input['foto_base64'])) {
|
||||
$imgData = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $input['foto_base64']));
|
||||
$filename = 'laporan_' . time() . '_' . rand(1000,9999) . '.jpg';
|
||||
$upload_dir = '../uploads/laporan/';
|
||||
if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
|
||||
file_put_contents($upload_dir . $filename, $imgData);
|
||||
$foto_url = 'uploads/laporan/' . $filename;
|
||||
}
|
||||
} else {
|
||||
$input = $_POST;
|
||||
}
|
||||
|
||||
if (empty($input['lat']) || empty($input['lng']) || empty($input['kategori_rusak'])) {
|
||||
echo json_encode(['success' => false, 'message' => 'Data tidak lengkap (lat, lng, kategori_rusak wajib)']);
|
||||
return;
|
||||
}
|
||||
|
||||
$lat = (float)$input['lat'];
|
||||
$lng = (float)$input['lng'];
|
||||
$jalan_id = (int)($input['jalan_id'] ?? 0);
|
||||
$nama_jalan = $db->real_escape_string($input['nama_jalan'] ?? 'Tidak Diketahui');
|
||||
$status_jalan = $db->real_escape_string($input['status_jalan'] ?? '');
|
||||
$nama_pelapor = $db->real_escape_string($input['nama_pelapor'] ?? 'Anonim');
|
||||
$kategori = $db->real_escape_string($input['kategori_rusak']);
|
||||
$tanggal = $db->real_escape_string($input['tanggal_laporan'] ?? date('Y-m-d'));
|
||||
$keterangan = $db->real_escape_string($input['keterangan'] ?? '');
|
||||
|
||||
// Build GeoJSON Point
|
||||
$geojson = json_encode([
|
||||
'type' => 'Feature',
|
||||
'properties' => [
|
||||
'nama_jalan' => $nama_jalan,
|
||||
'status_jalan' => $status_jalan,
|
||||
'kategori_rusak' => $kategori,
|
||||
'tanggal_laporan'=> $tanggal,
|
||||
'nama_pelapor' => $nama_pelapor,
|
||||
],
|
||||
'geometry' => [
|
||||
'type' => 'Point',
|
||||
'coordinates' => [$lng, $lat], // GeoJSON: [lng, lat]
|
||||
],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO laporan_kerusakan (jalan_id, nama_jalan, status_jalan, nama_pelapor, kategori_rusak, tanggal_laporan, keterangan, foto_url, geojson) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->bind_param('issssssss', $jalan_id, $nama_jalan, $status_jalan, $nama_pelapor, $kategori, $tanggal, $keterangan, $foto_url, $geojson);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'message' => 'Laporan berhasil disimpan', 'id' => $db->insert_id]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal menyimpan: ' . $db->error]);
|
||||
}
|
||||
$db->close();
|
||||
}
|
||||
|
||||
function updateLaporan($id) {
|
||||
if (!$id) { echo json_encode(['success' => false, 'message' => 'ID diperlukan']); return; }
|
||||
$db = getDB();
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$kategori = $db->real_escape_string($input['kategori_rusak'] ?? '');
|
||||
$tanggal = $db->real_escape_string($input['tanggal_laporan'] ?? date('Y-m-d'));
|
||||
$keterangan = $db->real_escape_string($input['keterangan'] ?? '');
|
||||
|
||||
$stmt = $db->prepare("UPDATE laporan_kerusakan SET kategori_rusak=?, tanggal_laporan=?, keterangan=?, updated_at=NOW() WHERE id=?");
|
||||
$stmt->bind_param('sssi', $kategori, $tanggal, $keterangan, $id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'message' => 'Laporan berhasil diperbarui']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal memperbarui: ' . $db->error]);
|
||||
}
|
||||
$db->close();
|
||||
}
|
||||
|
||||
function deleteLaporan($id) {
|
||||
if (!$id) { echo json_encode(['success' => false, 'message' => 'ID diperlukan']); return; }
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("DELETE FROM laporan_kerusakan WHERE id = ?");
|
||||
$stmt->bind_param('i', $id);
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'message' => 'Laporan berhasil dihapus']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal menghapus: ' . $db->error]);
|
||||
}
|
||||
$db->close();
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
// api/parsil.php - API CRUD untuk Data Parsil Tanah (Format GeoJSON)
|
||||
require_once 'config.php';
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
switch ($method) {
|
||||
case 'GET': getParsil($id); break;
|
||||
case 'POST': createParsil(); break;
|
||||
case 'PUT': updateParsil($id); break;
|
||||
case 'DELETE': deleteParsil($id); break;
|
||||
default:
|
||||
echo json_encode(['success' => false, 'message' => 'Method tidak diizinkan']);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Helper: baris DB → array siap kirim ke client
|
||||
// ─────────────────────────────────────────────
|
||||
function rowToResponse($row) {
|
||||
$feature = json_decode($row['geojson'], true);
|
||||
|
||||
// GeoJSON Polygon coords = array of rings, ring[0] = outer ring
|
||||
// Setiap titik [lng, lat] → ubah ke [lat, lng] untuk Leaflet
|
||||
// Hilangkan titik penutup (sama dengan titik pertama) yang GeoJSON haruskan
|
||||
$ring_geojson = $feature['geometry']['coordinates'][0] ?? [];
|
||||
$koordinat_leaflet = array_map(fn($c) => [$c[1], $c[0]], $ring_geojson);
|
||||
// Hapus titik penutup jika sama dengan titik pertama
|
||||
$n = count($koordinat_leaflet);
|
||||
if ($n > 1 && $koordinat_leaflet[0] === $koordinat_leaflet[$n - 1]) {
|
||||
array_pop($koordinat_leaflet);
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'nama_parsil' => $row['nomor_sertifikat'] ?? $row['nama_pemilik'] ?? 'Parsil',
|
||||
'nomor_sertifikat' => $row['nomor_sertifikat'],
|
||||
'status_kepemilikan' => $row['status_kepemilikan'],
|
||||
'luas_meter2' => (float)$row['luas_meter2'],
|
||||
'koordinat' => $koordinat_leaflet, // [lat,lng] untuk Leaflet
|
||||
'geojson' => $feature, // GeoJSON Feature asli
|
||||
'nama_pemilik' => $row['nama_pemilik'],
|
||||
'created_at' => $row['created_at'],
|
||||
'updated_at' => $row['updated_at'],
|
||||
];
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Helper: koordinat [[lat,lng],...] → GeoJSON Feature string
|
||||
// GeoJSON Polygon ring harus ditutup (titik pertama = terakhir)
|
||||
// ─────────────────────────────────────────────
|
||||
function buildGeoJSON($nosert, $status, $luas, $pemilik, $koordinat_leaflet) {
|
||||
// Leaflet: [lat, lng] → GeoJSON: [lng, lat]
|
||||
$coords = array_map(fn($c) => [(float)$c[1], (float)$c[0]], $koordinat_leaflet);
|
||||
|
||||
// Tutup ring: tambahkan titik pertama di akhir jika belum
|
||||
if (!empty($coords) && $coords[0] !== $coords[count($coords) - 1]) {
|
||||
$coords[] = $coords[0];
|
||||
}
|
||||
|
||||
$feature = [
|
||||
'type' => 'Feature',
|
||||
'properties' => [
|
||||
'nomor_sertifikat' => $nosert,
|
||||
'status_kepemilikan' => $status,
|
||||
'luas_meter2' => (float)$luas,
|
||||
'nama_pemilik' => $pemilik,
|
||||
],
|
||||
'geometry' => [
|
||||
'type' => 'Polygon',
|
||||
'coordinates' => [$coords], // array of rings
|
||||
],
|
||||
];
|
||||
return json_encode($feature, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
function getParsil($id = null) {
|
||||
$db = getDB();
|
||||
if ($id) {
|
||||
$stmt = $db->prepare("SELECT * FROM parsil_tanah WHERE id = ?");
|
||||
$stmt->bind_param('i', $id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$row = $result->fetch_assoc();
|
||||
if ($row) {
|
||||
echo json_encode(['success' => true, 'data' => rowToResponse($row)]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Data tidak ditemukan']);
|
||||
}
|
||||
} else {
|
||||
$result = $db->query("SELECT * FROM parsil_tanah ORDER BY created_at DESC");
|
||||
$rows = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$rows[] = rowToResponse($row);
|
||||
}
|
||||
echo json_encode(['success' => true, 'data' => $rows, 'total' => count($rows)]);
|
||||
}
|
||||
$db->close();
|
||||
}
|
||||
|
||||
function createParsil() {
|
||||
$db = getDB();
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (empty($input['status_kepemilikan']) || empty($input['koordinat'])) {
|
||||
echo json_encode(['success' => false, 'message' => 'Data tidak lengkap']);
|
||||
return;
|
||||
}
|
||||
|
||||
$nosert = $db->real_escape_string($input['nomor_sertifikat'] ?? '');
|
||||
$status = $db->real_escape_string($input['status_kepemilikan']);
|
||||
$luas = (float)($input['luas_meter2'] ?? 0);
|
||||
$pemilik = $db->real_escape_string($input['nama_pemilik'] ?? '');
|
||||
$geojson = buildGeoJSON($nosert, $status, $luas, $pemilik, $input['koordinat']);
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO parsil_tanah (nomor_sertifikat, status_kepemilikan, luas_meter2, geojson, nama_pemilik) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->bind_param('ssdss', $nosert, $status, $luas, $geojson, $pemilik);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'message' => 'Data parsil berhasil disimpan', 'id' => $db->insert_id]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal menyimpan data: ' . $db->error]);
|
||||
}
|
||||
$db->close();
|
||||
}
|
||||
|
||||
function updateParsil($id) {
|
||||
if (!$id) { echo json_encode(['success' => false, 'message' => 'ID diperlukan']); return; }
|
||||
$db = getDB();
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$nosert = $db->real_escape_string($input['nomor_sertifikat'] ?? '');
|
||||
$status = $db->real_escape_string($input['status_kepemilikan']);
|
||||
$luas = (float)($input['luas_meter2'] ?? 0);
|
||||
$pemilik = $db->real_escape_string($input['nama_pemilik'] ?? '');
|
||||
$geojson = buildGeoJSON($nosert, $status, $luas, $pemilik, $input['koordinat']);
|
||||
|
||||
$stmt = $db->prepare("UPDATE parsil_tanah SET nomor_sertifikat=?, status_kepemilikan=?, luas_meter2=?, geojson=?, nama_pemilik=?, updated_at=NOW() WHERE id=?");
|
||||
$stmt->bind_param('ssdssi', $nosert, $status, $luas, $geojson, $pemilik, $id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'message' => 'Data parsil berhasil diperbarui']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal memperbarui data: ' . $db->error]);
|
||||
}
|
||||
$db->close();
|
||||
}
|
||||
|
||||
function deleteParsil($id) {
|
||||
if (!$id) { echo json_encode(['success' => false, 'message' => 'ID diperlukan']); return; }
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("DELETE FROM parsil_tanah WHERE id = ?");
|
||||
$stmt->bind_param('i', $id);
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['success' => true, 'message' => 'Data parsil berhasil dihapus']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Gagal menghapus data']);
|
||||
}
|
||||
$db->close();
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,206 @@
|
||||
-- ============================================
|
||||
-- WebGIS Manajemen Jalan & Parsil Tanah
|
||||
-- Database Schema — Format GeoJSON
|
||||
-- ============================================
|
||||
|
||||
CREATE DATABASE IF NOT EXISTS polyline_polygon CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
USE polyline_polygon;
|
||||
|
||||
-- ============================================
|
||||
-- Tabel Data Jalan (LineString GeoJSON)
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS jalan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_jalan VARCHAR(255) NOT NULL,
|
||||
status_jalan ENUM('Jalan Nasional', 'Jalan Provinsi', 'Jalan Kabupaten') NOT NULL,
|
||||
panjang_meter DOUBLE DEFAULT 0,
|
||||
geojson LONGTEXT NOT NULL COMMENT 'GeoJSON Feature object with geometry type LineString',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ============================================
|
||||
-- Tabel Data Parsil Tanah (Polygon GeoJSON)
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS parsil_tanah (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nomor_sertifikat VARCHAR(100),
|
||||
status_kepemilikan ENUM('SHM', 'HGB', 'HGU', 'HP') NOT NULL,
|
||||
luas_meter2 DOUBLE DEFAULT 0,
|
||||
geojson LONGTEXT NOT NULL COMMENT 'GeoJSON Feature object with geometry type Polygon',
|
||||
nama_pemilik VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ============================================
|
||||
-- Tabel Laporan Kerusakan Jalan (Point GeoJSON)
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS laporan_kerusakan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
jalan_id INT DEFAULT 0 COMMENT 'FK ke tabel jalan (0 jika tidak terdeteksi)',
|
||||
nama_jalan VARCHAR(255) DEFAULT 'Tidak Diketahui',
|
||||
status_jalan VARCHAR(50) DEFAULT '',
|
||||
nama_pelapor VARCHAR(255) DEFAULT 'Anonim',
|
||||
kategori_rusak ENUM('Ringan', 'Sedang', 'Berat') NOT NULL,
|
||||
tanggal_laporan DATE NOT NULL,
|
||||
keterangan TEXT,
|
||||
foto_url VARCHAR(500) DEFAULT NULL COMMENT 'Path foto kerusakan yang diupload',
|
||||
geojson LONGTEXT NOT NULL COMMENT 'GeoJSON Feature Point [lng, lat]',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_jalan_id (jalan_id),
|
||||
INDEX idx_tanggal (tanggal_laporan),
|
||||
INDEX idx_kategori (kategori_rusak)
|
||||
);
|
||||
|
||||
-- ============================================
|
||||
-- Sample Data Jalan
|
||||
-- Catatan: GeoJSON menggunakan [lng, lat] (bukan [lat, lng])
|
||||
-- ============================================
|
||||
INSERT INTO jalan (nama_jalan, status_jalan, panjang_meter, geojson) VALUES
|
||||
(
|
||||
'Jalan Ahmad Yani',
|
||||
'Jalan Nasional',
|
||||
1250.5,
|
||||
'{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"nama_jalan": "Jalan Ahmad Yani",
|
||||
"status_jalan": "Jalan Nasional",
|
||||
"panjang_meter": 1250.5
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[109.3300, -0.0220],
|
||||
[109.3340, -0.0235],
|
||||
[109.3375, -0.0250],
|
||||
[109.3410, -0.0263]
|
||||
]
|
||||
}
|
||||
}'
|
||||
),
|
||||
(
|
||||
'Jalan Gajah Mada',
|
||||
'Jalan Provinsi',
|
||||
870.3,
|
||||
'{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"nama_jalan": "Jalan Gajah Mada",
|
||||
"status_jalan": "Jalan Provinsi",
|
||||
"panjang_meter": 870.3
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[109.3280, -0.0280],
|
||||
[109.3330, -0.0270],
|
||||
[109.3380, -0.0260]
|
||||
]
|
||||
}
|
||||
}'
|
||||
),
|
||||
(
|
||||
'Jalan Pahlawan',
|
||||
'Jalan Kabupaten',
|
||||
540.7,
|
||||
'{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"nama_jalan": "Jalan Pahlawan",
|
||||
"status_jalan": "Jalan Kabupaten",
|
||||
"panjang_meter": 540.7
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[109.3420, -0.0300],
|
||||
[109.3450, -0.0290],
|
||||
[109.3480, -0.0280]
|
||||
]
|
||||
}
|
||||
}'
|
||||
);
|
||||
|
||||
-- ============================================
|
||||
-- Sample Data Parsil Tanah
|
||||
-- Polygon: koordinat pertama dan terakhir harus sama (closed ring)
|
||||
-- ============================================
|
||||
INSERT INTO parsil_tanah (nomor_sertifikat, status_kepemilikan, luas_meter2, geojson, nama_pemilik) VALUES
|
||||
(
|
||||
'SHM-2024-001',
|
||||
'SHM',
|
||||
450.0,
|
||||
'{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"nomor_sertifikat": "SHM-2024-001",
|
||||
"status_kepemilikan": "SHM",
|
||||
"luas_meter2": 450.0,
|
||||
"nama_pemilik": "Budi Santoso"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[109.3355, -0.0255],
|
||||
[109.3370, -0.0255],
|
||||
[109.3370, -0.0245],
|
||||
[109.3355, -0.0245],
|
||||
[109.3355, -0.0255]
|
||||
]]
|
||||
}
|
||||
}',
|
||||
'Budi Santoso'
|
||||
),
|
||||
(
|
||||
'HGB-2024-002',
|
||||
'HGB',
|
||||
620.5,
|
||||
'{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"nomor_sertifikat": "HGB-2024-002",
|
||||
"status_kepemilikan": "HGB",
|
||||
"luas_meter2": 620.5,
|
||||
"nama_pemilik": "PT. Maju Jaya"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[109.3395, -0.0270],
|
||||
[109.3415, -0.0270],
|
||||
[109.3415, -0.0258],
|
||||
[109.3395, -0.0258],
|
||||
[109.3395, -0.0270]
|
||||
]]
|
||||
}
|
||||
}',
|
||||
'PT. Maju Jaya'
|
||||
),
|
||||
(
|
||||
'HGU-2024-003',
|
||||
'HGU',
|
||||
1200.0,
|
||||
'{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"nomor_sertifikat": "HGU-2024-003",
|
||||
"status_kepemilikan": "HGU",
|
||||
"luas_meter2": 1200.0,
|
||||
"nama_pemilik": "CV. Agro Lestari"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[109.3310, -0.0285],
|
||||
[109.3335, -0.0285],
|
||||
[109.3335, -0.0268],
|
||||
[109.3310, -0.0268],
|
||||
[109.3310, -0.0285]
|
||||
]]
|
||||
}
|
||||
}',
|
||||
'CV. Agro Lestari'
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user