diff --git a/app/Http/Controllers/Admin/SiswaController.php b/app/Http/Controllers/Admin/SiswaController.php index a2c8921..9d8adb0 100644 --- a/app/Http/Controllers/Admin/SiswaController.php +++ b/app/Http/Controllers/Admin/SiswaController.php @@ -32,7 +32,10 @@ public function store(Request $request) 'tanggal_lahir' => 'required|date', 'jenis_kelamin' => 'required|in:L,P', 'wali_murid_id' => 'required|exists:wali_murids,id', - // Alamat dihapus, karena ikut Wali Murid + + // --- TAMBAHAN BARU: Validasi Titik Koordinat Peta --- + 'latitude' => 'nullable|string', + 'longitude' => 'nullable|string', ]); Siswa::create([ @@ -43,6 +46,10 @@ public function store(Request $request) 'tanggal_lahir' => $request->tanggal_lahir, 'jenis_kelamin' => $request->jenis_kelamin, 'wali_murid_id' => $request->wali_murid_id, + + // --- TAMBAHAN BARU: Simpan ke Database --- + 'latitude' => $request->latitude, + 'longitude' => $request->longitude, ]); return redirect()->route('siswa.index')->with('success', 'Data Siswa berhasil ditambahkan.'); @@ -67,6 +74,10 @@ public function update(Request $request, $id) 'tanggal_lahir' => 'required|date', 'jenis_kelamin' => 'required|in:L,P', 'wali_murid_id' => 'required|exists:wali_murids,id', + + // --- TAMBAHAN BARU: Validasi Titik Koordinat Peta --- + 'latitude' => 'nullable|string', + 'longitude' => 'nullable|string', ]); $siswa->update([ @@ -77,6 +88,10 @@ public function update(Request $request, $id) 'tanggal_lahir' => $request->tanggal_lahir, 'jenis_kelamin' => $request->jenis_kelamin, 'wali_murid_id' => $request->wali_murid_id, + + // --- TAMBAHAN BARU: Update ke Database --- + 'latitude' => $request->latitude, + 'longitude' => $request->longitude, ]); return redirect()->route('siswa.index')->with('success', 'Data Siswa berhasil diperbarui!'); diff --git a/app/Http/Controllers/Api/AStarController.php b/app/Http/Controllers/Api/AStarController.php new file mode 100644 index 0000000..c69661b --- /dev/null +++ b/app/Http/Controllers/Api/AStarController.php @@ -0,0 +1,142 @@ +where('id', $id_awal)->first(); + + // 1. Ambil kordinat dari HP Flutter + $latTujuan = $request->query('lat'); + $lngTujuan = $request->query('lng'); + + if (!$latTujuan || !$lngTujuan) { + return response()->json(['success' => false, 'message' => 'Koordinat tidak valid'], 400); + } + + $semua_titik = DB::table('titik_jalans')->get()->keyBy('id'); + + // 2. CARI TITIK TUJUAN YANG PALING COCOK BERDASARKAN KOORDINAT + $tujuan = null; + $jarakTerdekat = INF; + + foreach ($semua_titik as $titik) { + $jarak = $this->hitungJarak($latTujuan, $lngTujuan, $titik->latitude, $titik->longitude); + if ($jarak < $jarakTerdekat) { + $jarakTerdekat = $jarak; + $tujuan = $titik; + } + } + + if (!$tujuan) { + return response()->json(['success' => false, 'message' => 'Titik tidak ditemukan'], 404); + } + + $id_tujuan = $tujuan->id; // Dapatkan ID aslinya di tabel titik_jalans + + // 3. Ambil jalur jembatannya + $edges = DB::table('jalur_jalans')->get(); + $graph = []; + foreach ($edges as $edge) { + $graph[$edge->titik_awal_id][] = [ + 'tujuan' => $edge->titik_tujuan_id, + 'jarak' => $edge->jarak + ]; + } + + // ========================================== + // PROSES A-STAR (A*) + // ========================================== + $openList = [$id_awal]; + $closedList = []; + $cameFrom = []; + + $gCost = []; + $fCost = []; + foreach ($semua_titik as $id => $titik) { + $gCost[$id] = INF; + $fCost[$id] = INF; + } + $gCost[$id_awal] = 0; + $fCost[$id_awal] = $this->hitungHeuristic($awal, $tujuan); + + while (!empty($openList)) { + $current = null; + $lowestF = INF; + foreach ($openList as $nodeId) { + if ($fCost[$nodeId] < $lowestF) { + $lowestF = $fCost[$nodeId]; + $current = $nodeId; + } + } + + if ($current == $id_tujuan) { + return $this->rekonstruksiRute($cameFrom, $current, $semua_titik, $gCost[$current]); + } + + $openList = array_diff($openList, [$current]); + $closedList[] = $current; + + if (isset($graph[$current])) { + foreach ($graph[$current] as $neighbor) { + $neighborId = $neighbor['tujuan']; + + if (in_array($neighborId, $closedList)) continue; + + $tentativeGCost = $gCost[$current] + $neighbor['jarak']; + + if ($tentativeGCost < $gCost[$neighborId]) { + $cameFrom[$neighborId] = $current; + $gCost[$neighborId] = $tentativeGCost; + + $hCost = $this->hitungHeuristic($semua_titik[$neighborId], $tujuan); + $fCost[$neighborId] = $tentativeGCost + $hCost; + + if (!in_array($neighborId, $openList)) { + $openList[] = $neighborId; + } + } + } + } + } + + return response()->json(['success' => false, 'message' => 'Rute tidak ditemukan (Buntu)'], 404); + } + + // --- RUMUS BANTUAN --- + private function hitungJarak($lat1, $lon1, $lat2, $lon2) { + $earthRadius = 6371000; + $latFrom = deg2rad((float)$lat1); + $lonFrom = deg2rad((float)$lon1); + $latTo = deg2rad((float)$lat2); + $lonTo = deg2rad((float)$lon2); + + $latDelta = $latTo - $latFrom; + $lonDelta = $lonTo - $lonFrom; + + $angle = 2 * asin(sqrt(pow(sin($latDelta / 2), 2) + cos($latFrom) * cos($latTo) * pow(sin($lonDelta / 2), 2))); + return round($angle * $earthRadius); + } + + private function hitungHeuristic($titikA, $titikB) { + return $this->hitungJarak($titikA->latitude, $titikA->longitude, $titikB->latitude, $titikB->longitude); + } + + private function rekonstruksiRute($cameFrom, $current, $semua_titik, $jarakTotal) { + $rute = []; + while (isset($cameFrom[$current])) { + array_unshift($rute, $semua_titik[$current]); + $current = $cameFrom[$current]; + } + array_unshift($rute, $semua_titik[$current]); + + return response()->json(['success' => true, 'jarak_total_meter' => $jarakTotal, 'titik_rute' => $rute]); + } +} \ No newline at end of file diff --git a/app/Models/JalurJalan.php b/app/Models/JalurJalan.php new file mode 100644 index 0000000..d9415ba --- /dev/null +++ b/app/Models/JalurJalan.php @@ -0,0 +1,10 @@ +string('latitude')->nullable()->after('alamat'); + $table->string('longitude')->nullable()->after('latitude'); + }); + } + + public function down() + { + Schema::table('siswas', function (Blueprint $table) { + $table->dropColumn(['latitude', 'longitude']); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_02_23_163329_create_kunjungans_table.php b/database/migrations/2026_02_23_163329_create_kunjungans_table.php new file mode 100644 index 0000000..6b0271e --- /dev/null +++ b/database/migrations/2026_02_23_163329_create_kunjungans_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignId('guru_id')->constrained('users')->onDelete('cascade'); // Relasi ke Guru (User) + $table->foreignId('siswa_id')->constrained('siswas')->onDelete('cascade'); // Relasi ke Siswa + $table->date('tanggal_visit'); + $table->string('status')->default('menunggu'); // Status: menunggu, jalan, selesai + + // --- Kolom untuk Penilaian (Dibuat fleksibel) --- + $table->string('materi_belajar')->nullable(); + $table->string('nilai')->nullable(); // String, biar bisa diisi "BB", "BSB", atau angka 80 + $table->text('catatan')->nullable(); + $table->string('foto_kegiatan')->nullable(); + + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('kunjungans'); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_02_25_014445_create_titik_jalans_table.php b/database/migrations/2026_02_25_014445_create_titik_jalans_table.php new file mode 100644 index 0000000..5c0bd12 --- /dev/null +++ b/database/migrations/2026_02_25_014445_create_titik_jalans_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('nama_titik'); // Contoh: "Simpang 3 Diponegoro", "PAUD", "Rumah Achazia" + $table->string('latitude'); + $table->string('longitude'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('titik_jalans'); + } +}; diff --git a/database/migrations/2026_02_25_014540_create_jalur_jalans_table.php b/database/migrations/2026_02_25_014540_create_jalur_jalans_table.php new file mode 100644 index 0000000..1893d5e --- /dev/null +++ b/database/migrations/2026_02_25_014540_create_jalur_jalans_table.php @@ -0,0 +1,33 @@ +id(); + // Titik awal persimpangan + $table->foreignId('titik_awal_id')->constrained('titik_jalans')->onDelete('cascade'); + // Titik tujuan persimpangan + $table->foreignId('titik_tujuan_id')->constrained('titik_jalans')->onDelete('cascade'); + // Jarak asli jalan raya (dalam meter / kilometer) -> Ini jadi G-Cost di A* + $table->double('jarak'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('jalur_jalans'); + } +}; diff --git a/database/seeders/JalurJalanSeeder.php b/database/seeders/JalurJalanSeeder.php new file mode 100644 index 0000000..872befb --- /dev/null +++ b/database/seeders/JalurJalanSeeder.php @@ -0,0 +1,87 @@ +truncate(); + DB::statement('SET FOREIGN_KEY_CHECKS=1;'); + + // 2. Ambil semua data titik yang sudah Ghoza input + $titik = DB::table('titik_jalans')->get()->keyBy('id'); + $jalur = []; + + // 3. Hubungkan PAUD (ID 1) dengan Simpang A (2) dan Simpang B (3) + $jalur = array_merge($jalur, $this->buatJalurBolakBalik($titik[1], $titik[2])); + $jalur = array_merge($jalur, $this->buatJalurBolakBalik($titik[1], $titik[3])); + + // 4. Hubungkan SETIAP RUMAH SISWA (ID 4 sampai 59) ke titik terdekatnya + for ($i = 4; $i <= 59; $i++) { + if(!isset($titik[$i])) continue; // Lewati kalau ID tidak ada + + $rumah = $titik[$i]; + + // Hitung jarak dari rumah ke PAUD & Persimpangan + $jarakKePaud = $this->hitungJarak($rumah->latitude, $rumah->longitude, $titik[1]->latitude, $titik[1]->longitude); + $jarakKeSimpangA = $this->hitungJarak($rumah->latitude, $rumah->longitude, $titik[2]->latitude, $titik[2]->longitude); + $jarakKeSimpangB = $this->hitungJarak($rumah->latitude, $rumah->longitude, $titik[3]->latitude, $titik[3]->longitude); + + // Cari mana yang paling dekat + $terdekat = 1; + $jarakMin = $jarakKePaud; + + if ($jarakKeSimpangA < $jarakMin) { + $terdekat = 2; + $jarakMin = $jarakKeSimpangA; + } + if ($jarakKeSimpangB < $jarakMin) { + $terdekat = 3; + $jarakMin = $jarakKeSimpangB; + } + + // Buat jembatan bolak-balik dari rumah ke titik terdekat tersebut + $jalur = array_merge($jalur, $this->buatJalurBolakBalik($rumah, $titik[$terdekat])); + } + + // 5. Simpan semua data jembatannya ke Database! + DB::table('jalur_jalans')->insert($jalur); + + $totalJalur = count($jalur); + $this->command->info("WOW! Berhasil membuat {$totalJalur} jembatan rute secara otomatis pakai Haversine Formula!"); + } + + // --- RUMUS BANTUAN --- + + // Fungsi bikin jalur 2 arah (Pergi - Pulang) + private function buatJalurBolakBalik($titikA, $titikB) { + $jarak = $this->hitungJarak($titikA->latitude, $titikA->longitude, $titikB->latitude, $titikB->longitude); + return [ + ['titik_awal_id' => $titikA->id, 'titik_tujuan_id' => $titikB->id, 'jarak' => $jarak], + ['titik_awal_id' => $titikB->id, 'titik_tujuan_id' => $titikA->id, 'jarak' => $jarak], + ]; + } + + // Rumus Haversine: Mengubah Latitude & Longitude menjadi jarak Meter aslinya + private function hitungJarak($lat1, $lon1, $lat2, $lon2) { + $earthRadius = 6371000; // Radius bumi dalam meter + $latFrom = deg2rad((float)$lat1); + $lonFrom = deg2rad((float)$lon1); + $latTo = deg2rad((float)$lat2); + $lonTo = deg2rad((float)$lon2); + + $latDelta = $latTo - $latFrom; + $lonDelta = $lonTo - $lonFrom; + + $angle = 2 * asin(sqrt(pow(sin($latDelta / 2), 2) + + cos($latFrom) * cos($latTo) * pow(sin($lonDelta / 2), 2))); + + return round($angle * $earthRadius); // Dibulatkan + } +} \ No newline at end of file diff --git a/resources/views/admin/siswa/create.blade.php b/resources/views/admin/siswa/create.blade.php index 0b8f80b..ad370c1 100644 --- a/resources/views/admin/siswa/create.blade.php +++ b/resources/views/admin/siswa/create.blade.php @@ -1,7 +1,7 @@ @extends('layouts.app') @section('content') -
+

➕ Tambah Siswa Baru

← Kembali @@ -74,6 +74,24 @@
+
+ +

Geser dan klik pada peta di bawah untuk menandai rumah siswa.

+ +
+ +
+
+ + +
+
+ + +
+
+
+
+ + + + @endsection \ No newline at end of file diff --git a/resources/views/admin/siswa/edit.blade.php b/resources/views/admin/siswa/edit.blade.php index acdec3e..9d4616a 100644 --- a/resources/views/admin/siswa/edit.blade.php +++ b/resources/views/admin/siswa/edit.blade.php @@ -1,7 +1,7 @@ @extends('layouts.app') @section('content') -
+

✏️ Edit Data Siswa

← Kembali @@ -78,6 +78,24 @@
+
+ +

Geser dan klik pada peta untuk mengubah lokasi rumah siswa.

+ +
+ +
+
+ + +
+
+ + +
+
+
+
+ + + + @endsection \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index d906845..9570c17 100644 --- a/routes/api.php +++ b/routes/api.php @@ -10,6 +10,7 @@ use App\Http\Controllers\Api\LaporanController; use App\Http\Controllers\Api\GuruController; use App\Http\Controllers\Api\PenjemputanController; +use App\Http\Controllers\Api\AStarController; /* |-------------------------------------------------------------------------- @@ -26,7 +27,6 @@ // --- KHUSUS WALI MURID (Lihat Data) --- // Wali melihat daftar anaknya - // ========================================== // 2. AREA TERKUNCI (BUTUH TOKEN) // ========================================== @@ -55,5 +55,8 @@ // RUTE PENJEMPUTAN BARU Route::post('/penjemputan', [PenjemputanController::class, 'store']); + + Route::get('/rute-astar', [AStarController::class, 'cariRute']); + }); diff --git a/routes/web.php b/routes/web.php index 181cc7f..04e0c5c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -82,4 +82,30 @@ Route::get('/rapot/{id}/print', [App\Http\Controllers\Admin\RapotController::class, 'print'])->name('rapot.print'); }); + Route::get('/sinkron-kordinat', function () { + // Ambil data dari ID 4 sampai akhir (karena ID 1-3 itu PAUD dan Simpang) + $titikRumah = DB::table('titik_jalans')->where('id', '>=', 4)->get(); + $berhasil = 0; + + foreach ($titikRumah as $titik) { + // Hilangkan kata "Rumah " biar sisa nama siswanya aja + $namaSiswa = str_replace('Rumah ', '', $titik->nama_titik); + $namaSiswa = trim($namaSiswa); + + // Update latitude & longitude di tabel siswas yang namanya mirip + $update = DB::table('siswas') + ->where('nama_siswa', 'LIKE', '%' . $namaSiswa . '%') + ->update([ + 'latitude' => $titik->latitude, + 'longitude' => $titik->longitude + ]); + + if($update) { + $berhasil++; + } + } + + return "Selesai Bosku! Berhasil menyinkronkan $berhasil data koordinat siswa!"; +}); + }); \ No newline at end of file