pembaruan sig tps

This commit is contained in:
rahmagustin 2026-02-09 14:43:26 +07:00
parent d54ab1258e
commit ccf66298ab
6 changed files with 457 additions and 434 deletions

View File

@ -10,66 +10,47 @@
class TpsController extends Controller class TpsController extends Controller
{ {
public function index() public function index()
{ {
$title = 'Data TPS'; $title = 'Data TPS';
// Ambil TPS + kategori + jumlah aduan
$tps = LokasiTps::with('kategori') $tps = LokasiTps::with('kategori')
->withCount('aduan') ->withCount('aduan')
->get(); ->get();
return view('admin.tps.index', compact('title', 'tps')); return view('admin.tps.index', compact('title', 'tps'));
} }
public function create() public function create()
{ {
$title = 'Tambah TPS'; $title = 'Tambah TPS';
$kategori = KategoriTps::all(); $kategori = KategoriTps::all();
return view('admin.tps.create', compact('title', 'kategori')); return view('admin.tps.create', compact('title', 'kategori'));
} }
private function convertToDecimal($coordinate)
private function convertToDecimal($coordinate) {
{
// decimal langsung
if (is_numeric($coordinate)) { if (is_numeric($coordinate)) {
return (float) $coordinate; return (float) $coordinate;
} }
$coordinate = html_entity_decode($coordinate); $coordinate = html_entity_decode($coordinate);
$coordinate = strtoupper(trim($coordinate)); $coordinate = strtoupper(trim($coordinate));
// ganti simbol jadi seragam
$coordinate = str_replace( $coordinate = str_replace(
['°', "'", '"'], ['°', "'", '"'],
[' ', ' ', ' '], [' ', ' ', ' '],
$coordinate $coordinate
); );
// ambil arah
preg_match('/([NSEW])/', $coordinate, $dirMatch); preg_match('/([NSEW])/', $coordinate, $dirMatch);
if (!$dirMatch) return null; if (!$dirMatch) return null;
$direction = $dirMatch[1]; $direction = $dirMatch[1];
// ambil angka
preg_match_all('/\d+(\.\d+)?/', $coordinate, $numbers); preg_match_all('/\d+(\.\d+)?/', $coordinate, $numbers);
if (count($numbers[0]) < 3) return null; if (count($numbers[0]) < 3) return null;
[$deg, $min, $sec] = array_map('floatval', $numbers[0]); [$deg, $min, $sec] = array_map('floatval', $numbers[0]);
$decimal = $deg + ($min / 60) + ($sec / 3600); $decimal = $deg + ($min / 60) + ($sec / 3600);
if (in_array($direction, ['S', 'W'])) { if (in_array($direction, ['S', 'W'])) {
$decimal *= -1; $decimal *= -1;
} }
return $decimal; return $decimal;
} }
public function store(Request $request)
public function store(Request $request) {
{
$request->validate([ $request->validate([
'kategori_tps_id' => 'required|exists:kategori_tps,id_kategori_tps', 'kategori_tps_id' => 'required|exists:kategori_tps,id_kategori_tps',
'nama_tps' => 'required|string|max:255', 'nama_tps' => 'required|string|max:255',
@ -81,20 +62,14 @@ public function store(Request $request)
'longitude' => 'required', 'longitude' => 'required',
'foto_tps' => 'nullable|image|mimes:jpg,jpeg,png|max:2048', 'foto_tps' => 'nullable|image|mimes:jpg,jpeg,png|max:2048',
]); ]);
// 🔥 KONVERSI KOORDINAT
$latitude = $this->convertToDecimal($request->latitude); $latitude = $this->convertToDecimal($request->latitude);
$longitude = $this->convertToDecimal($request->longitude); $longitude = $this->convertToDecimal($request->longitude);
if ($latitude === null || $longitude === null) { if ($latitude === null || $longitude === null) {
return back()->withErrors(['Koordinat tidak valid'])->withInput(); return back()->withErrors(['Koordinat tidak valid'])->withInput();
} }
// Upload foto
$foto = $request->hasFile('foto_tps') $foto = $request->hasFile('foto_tps')
? $request->file('foto_tps')->store('foto-tps', 'public') ? $request->file('foto_tps')->store('foto-tps', 'public')
: null; : null;
LokasiTps::create([ LokasiTps::create([
'kategori_tps_id' => $request->kategori_tps_id, 'kategori_tps_id' => $request->kategori_tps_id,
'nama_tps' => $request->nama_tps, 'nama_tps' => $request->nama_tps,
@ -106,26 +81,20 @@ public function store(Request $request)
'longitude' => $longitude, 'longitude' => $longitude,
'foto_tps' => $foto, 'foto_tps' => $foto,
]); ]);
return redirect()->route('admin.tps.index') return redirect()->route('admin.tps.index')
->with('success', 'Data TPS berhasil ditambahkan'); ->with('success', 'Data TPS berhasil ditambahkan');
} }
public function edit($id)
public function edit($id) {
{
$title = 'Edit TPS'; $title = 'Edit TPS';
$tps = LokasiTps::findOrFail($id); $tps = LokasiTps::findOrFail($id);
$kategori = KategoriTps::all(); $kategori = KategoriTps::all();
return view('admin.tps.edit', compact('title', 'tps', 'kategori')); return view('admin.tps.edit', compact('title', 'tps', 'kategori'));
} }
public function update(Request $request, $id)
public function update(Request $request, $id) {
{
$tps = LokasiTps::findOrFail($id); $tps = LokasiTps::findOrFail($id);
// VALIDASI
$request->validate([ $request->validate([
'kategori_tps_id' => 'required|exists:kategori_tps,id_kategori_tps', 'kategori_tps_id' => 'required|exists:kategori_tps,id_kategori_tps',
'nama_tps' => 'required|string|max:255', 'nama_tps' => 'required|string|max:255',
@ -137,32 +106,21 @@ public function update(Request $request, $id)
'longitude' => 'required', 'longitude' => 'required',
'foto_tps' => 'nullable|image|mimes:jpg,jpeg,png|max:4096', 'foto_tps' => 'nullable|image|mimes:jpg,jpeg,png|max:4096',
]); ]);
// 🔥 KONVERSI KOORDINAT (DMS / DECIMAL)
$latitude = $this->convertToDecimal($request->latitude); $latitude = $this->convertToDecimal($request->latitude);
$longitude = $this->convertToDecimal($request->longitude); $longitude = $this->convertToDecimal($request->longitude);
// Jika koordinat tidak valid
if ($latitude === null || $longitude === null) { if ($latitude === null || $longitude === null) {
return back() return back()
->withErrors(['Koordinat tidak valid. Gunakan format Decimal atau DMS.']) ->withErrors(['Koordinat tidak valid. Gunakan format Decimal atau DMS.'])
->withInput(); ->withInput();
} }
// 📸 UPLOAD FOTO JIKA ADA
if ($request->hasFile('foto_tps')) { if ($request->hasFile('foto_tps')) {
// Hapus foto lama
if ($tps->foto_tps) { if ($tps->foto_tps) {
Storage::disk('public')->delete($tps->foto_tps); Storage::disk('public')->delete($tps->foto_tps);
} }
$foto = $request->file('foto_tps')->store('foto-tps', 'public'); $foto = $request->file('foto_tps')->store('foto-tps', 'public');
} else { } else {
$foto = $tps->foto_tps; $foto = $tps->foto_tps;
} }
// 💾 UPDATE DATA
$tps->update([ $tps->update([
'kategori_tps_id' => $request->kategori_tps_id, 'kategori_tps_id' => $request->kategori_tps_id,
'nama_tps' => $request->nama_tps, 'nama_tps' => $request->nama_tps,
@ -174,22 +132,18 @@ public function update(Request $request, $id)
'longitude' => $longitude, 'longitude' => $longitude,
'foto_tps' => $foto, 'foto_tps' => $foto,
]); ]);
return redirect()->route('admin.tps.index') return redirect()->route('admin.tps.index')
->with('success', 'Data TPS berhasil diperbarui'); ->with('success', 'Data TPS berhasil diperbarui');
} }
public function destroy($id) public function destroy($id)
{ {
$tps = LokasiTps::findOrFail($id); $tps = LokasiTps::findOrFail($id);
if ($tps->foto_tps) { if ($tps->foto_tps) {
Storage::disk('public')->delete($tps->foto_tps); Storage::disk('public')->delete($tps->foto_tps);
} }
$tps->delete(); $tps->delete();
return redirect()->route('admin.tps.index') return redirect()->route('admin.tps.index')
->with('success', 'Data TPS berhasil dihapus'); ->with('success', 'Data TPS berhasil dihapus');
} }
} }

View File

@ -8,22 +8,15 @@
use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use App\Models\User;
class LoginRequest extends FormRequest class LoginRequest extends FormRequest
{ {
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool public function authorize(): bool
{ {
return true; return true;
} }
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array public function rules(): array
{ {
return [ return [
@ -32,31 +25,25 @@ public function rules(): array
]; ];
} }
/** public function authenticate(): void
* Attempt to authenticate the request's credentials. {
* $user = User::where('username', $this->username)->first();
* @throws \Illuminate\Validation\ValidationException if (!$user) {
*/
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('username', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'email' => trans('auth.failed'), 'username' => 'Username tidak terdaftar.',
]); ]);
} }
if (!Auth::attempt([
RateLimiter::clear($this->throttleKey()); 'username' => $this->username,
'password' => $this->password,
])) {
throw ValidationException::withMessages([
'password' => 'Password yang dimasukkan salah.',
]);
} }
}
/**
* Ensure the login request is not rate limited.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function ensureIsNotRateLimited(): void public function ensureIsNotRateLimited(): void
{ {
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
@ -68,18 +55,16 @@ public function ensureIsNotRateLimited(): void
$seconds = RateLimiter::availableIn($this->throttleKey()); $seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'email' => trans('auth.throttle', [ 'username' => trans('auth.throttle', [
'seconds' => $seconds, 'seconds' => $seconds,
'minutes' => ceil($seconds / 60), 'minutes' => ceil($seconds / 60),
]), ]),
]); ]);
} }
/**
* Get the rate limiting throttle key for the request.
*/
public function throttleKey(): string public function throttleKey(): string
{ {
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip()); return Str::transliterate(Str::lower($this->string('username')) . '|' . $this->ip());
} }
} }

View File

@ -3027,3 +3027,44 @@ .btn-aduan-tps:hover {
background: #bb2d3b; /* merah lebih gelap */ background: #bb2d3b; /* merah lebih gelap */
color: #fff; color: #fff;
} }
.map-wrapper {
position: relative;
}
.map-action {
position: absolute;
top: 16px;
right: 16px;
z-index: 1000;
background: #fff;
padding: 12px 14px;
border-radius: 12px;
box-shadow: 0 6px 20px rgba(0,0,0,.15);
max-width: 260px;
}
.map-action-text {
font-size: 14px;
color: #333;
margin-bottom: 8px;
line-height: 1.4;
}
.btn-lokasi {
width: 100%;
border: none;
background: #00A86B;
color: #fff;
padding: 8px 12px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
}
.btn-lokasi:hover,
.btn-lokasi:focus:hover {
color: var(--contrast-color);
background: color-mix(in srgb, var(--accent-color), transparent 15%);
}

View File

@ -7,10 +7,10 @@
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="mb-3 d-flex justify-content-between align-items-center">
<div> <div>
<h4 class="card-title mb-0">Data TPS</h4> <h4 class="mb-0 card-title">Data TPS</h4>
<p class="card-description mb-0"> <p class="mb-0 card-description">
Daftar Tempat Pembuangan Sampah (TPS) Daftar Tempat Pembuangan Sampah (TPS)
</p> </p>
</div> </div>
@ -20,7 +20,7 @@
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>Nama TPS</th> <th>Nama TPS</th>
@ -31,18 +31,12 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@forelse ($tps as $item) @forelse ($tps as $item)
<tr> <tr>
<!-- NAMA -->
<td>{{ $item->nama_tps }}</td> <td>{{ $item->nama_tps }}</td>
<!-- KATEGORI -->
<td> <td>
{{ $item->kategori->nama_kategori ?? '-' }} {{ $item->kategori->nama_kategori ?? '-' }}
</td> </td>
<!-- FOTO -->
<td> <td>
@if ($item->foto_tps) @if ($item->foto_tps)
<img src="{{ asset('storage/' . $item->foto_tps) }}" alt="Foto TPS" <img src="{{ asset('storage/' . $item->foto_tps) }}" alt="Foto TPS"
@ -55,9 +49,6 @@
<span class="text-muted">-</span> <span class="text-muted">-</span>
@endif @endif
</td> </td>
<!-- STATUS -->
<td> <td>
@if ($item->status_tps == 'Aktif') @if ($item->status_tps == 'Aktif')
<label class="badge badge-success">Aktif</label> <label class="badge badge-success">Aktif</label>
@ -67,14 +58,11 @@
<label class="badge badge-warning">Pembangunan</label> <label class="badge badge-warning">Pembangunan</label>
@endif @endif
</td> </td>
<!-- AKSI -->
<td class="text-center"> <td class="text-center">
<a href="{{ route('admin.tps.edit', $item->id_tps) }}" <a href="{{ route('admin.tps.edit', $item->id_tps) }}"
class="btn btn-warning btn-sm me-1"> class="btn btn-warning btn-sm me-1">
<i class="bi bi-pencil-square"></i> <i class="bi bi-pencil-square"></i>
</a> </a>
<form action="{{ route('admin.tps.destroy', $item->id_tps) }}" method="POST" <form action="{{ route('admin.tps.destroy', $item->id_tps) }}" method="POST"
class="form-hapus" style="display:inline;"> class="form-hapus" style="display:inline;">
@csrf @csrf
@ -83,7 +71,6 @@ class="form-hapus" style="display:inline;">
<i class="bi bi-trash"></i> <i class="bi bi-trash"></i>
</button> </button>
</form> </form>
</td> </td>
</tr> </tr>
@empty @empty
@ -93,9 +80,8 @@ class="form-hapus" style="display:inline;">
</td> </td>
</tr> </tr>
@endforelse @endforelse
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>

View File

@ -0,0 +1,67 @@
<?php
public function create()
{
$title = 'Tambah Data Sampah';
$users = User::all();
return view('admin.sampah.create', compact('title', 'users'));
}
public function store(Request $request)
{
$request->validate([
'tahun' => 'required|numeric',
'total_sampah' => 'required|numeric',
'total_kelola' => 'required|numeric',
'total_daur_ulang' => 'required|numeric',
]);
$sisa_sampah = $request->total_sampah
- ($request->total_kelola + $request->total_daur_ulang);
Sampah::create([
'user_id' => Auth::id(),
'tahun' => $request->tahun,
'total_sampah' => $request->total_sampah,
'total_kelola' => $request->total_kelola,
'total_daur_ulang' => $request->total_daur_ulang,
'sisa_sampah' => $sisa_sampah,
]);
return redirect()->route('admin.sampah.index')
->with('success', 'Data sampah berhasil ditambahkan');
}
public function edit($id)
{
$title = 'Edit Data Sampah';
$sampah = Sampah::findOrFail($id);
$users = User::all();
return view('admin.sampah.edit', compact('title', 'sampah', 'users'));
}
public function update(Request $request, $id)
{
$sampah = Sampah::findOrFail($id);
$request->validate([
'user_id' => 'required|exists:users,id',
'tahun' => 'required|numeric',
'total_sampah' => 'required|numeric',
'total_kelola' => 'required|numeric',
'total_daur_ulang' => 'required|numeric',
]);
$sisa_sampah = $request->total_sampah
- ($request->total_kelola + $request->total_daur_ulang);
$sampah->update([
'user_id' => $request->user_id,
'tahun' => $request->tahun,
'total_sampah' => $request->total_sampah,
'total_kelola' => $request->total_kelola,
'total_daur_ulang' => $request->total_daur_ulang,
'sisa_sampah' => $sisa_sampah,
]);
return redirect()->route('admin.sampah.index')
->with('success', 'Data sampah berhasil diperbarui');
}
public function destroy($id)
{
$sampah = Sampah::findOrFail($id);
$sampah->delete();
return redirect()->route('admin.sampah.index')
->with('success', 'Data sampah berhasil dihapus');
}

View File

@ -5,7 +5,9 @@
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style> <style>
header, .navbar, #header { header,
.navbar,
#header {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 2000 !important; z-index: 2000 !important;
@ -22,20 +24,7 @@
width: 100%; width: 100%;
height: 450px; height: 450px;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 6px 20px rgba(0,0,0,.12); box-shadow: 0 6px 20px rgba(0, 0, 0, .12);
}
.map-filter {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 12px;
align-items: center;
}
.map-info {
font-size: 14px;
color: #555;
} }
.legend { .legend {
@ -43,7 +32,7 @@
padding: 10px 12px; padding: 10px 12px;
border-radius: 10px; border-radius: 10px;
font-size: 14px; font-size: 14px;
box-shadow: 0 2px 12px rgba(0,0,0,.15); box-shadow: 0 2px 12px rgba(0, 0, 0, .15);
line-height: 18px; line-height: 18px;
} }
@ -53,6 +42,11 @@
float: left; float: left;
margin-right: 8px; margin-right: 8px;
} }
.user-location {
filter: drop-shadow(0 0 8px rgba(13,110,253,0.8))
drop-shadow(0 0 16px rgba(13,110,253,0.6));
}
</style> </style>
<div class="page-title"> <div class="page-title">
@ -69,37 +63,30 @@
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<div class="map-wrapper">
<!-- INFO & BUTTON --> <div class="map-action">
<div class="map-filter"> <div class="map-action-text">
<div class="map-info"> Temukan <b>TPS terdekat</b> dari lokasi Anda saat ini
Klik tombol di samping untuk menemukan <b>TPS terdekat</b> dari lokasi Anda saat ini.
</div> </div>
<button type="button" id="btnLokasi" class="btn-lokasi">
<button id="btnLokasi" class="btn btn-primary btn-sm">
Cari TPS Terdekat Cari TPS Terdekat
</button> </button>
</div> </div>
<div id="mapTPS"></div> <div id="mapTPS"></div>
</div> </div>
</div>
</section> </section>
<script> <script>
/* ============================= var map = L.map('mapTPS').setView([-7.6078, 111.903], 12);
INIT MAP
============================= */
var map = L.map('mapTPS').setView([-7.6078, 111.903], 12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19, maxZoom: 19,
attribution: '© OpenStreetMap' attribution: '© OpenStreetMap'
}).addTo(map); }).addTo(map);
/* ============================= function icon(color) {
ICON TPS
============================= */
function icon(color) {
return new L.Icon({ return new L.Icon({
iconUrl: `https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-${color}.png`, iconUrl: `https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-${color}.png`,
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png', shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
@ -107,23 +94,24 @@ function icon(color) {
iconAnchor: [12, 41], iconAnchor: [12, 41],
popupAnchor: [1, -34] popupAnchor: [1, -34]
}); });
} }
var iconTPS = icon('green'); var iconTPS = icon('green');
var iconTPS3R = icon('blue'); var iconTPS3R = icon('blue');
var iconTPA = icon('red'); var iconTPA = icon('red');
var iconNear = icon('yellow');
/* ============================= var iconNear = new L.Icon({
DATA TPS iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-gold.png',
============================= */ shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
var tpsData = @json($tps); iconSize: [25, 41],
var markers = []; iconAnchor: [12, 41],
popupAnchor: [1, -34]
});
/* ============================= var tpsData = @json($tps);
MARKER TPS var markers = [];
============================= */
tpsData.forEach(tps => { tpsData.forEach(tps => {
if (!tps.latitude || !tps.longitude) return; if (!tps.latitude || !tps.longitude) return;
let iconUse = iconTPS; let iconUse = iconTPS;
@ -134,7 +122,7 @@ function icon(color) {
.bindPopup(` .bindPopup(`
<strong>${tps.nama_tps}</strong><br> <strong>${tps.nama_tps}</strong><br>
<small>${tps.alamat_tps ?? '-'}</small><br> <small>${tps.alamat_tps ?? '-'}</small><br>
<span class="mt-1 badge bg-success">${tps.status_tps ?? '-'}</span> <span class="badge bg-success">${tps.status_tps ?? '-'}</span>
<hr style="margin:6px 0"> <hr style="margin:6px 0">
<a href="/tps/${tps.id_tps}" style="font-size:13px">Detail TPS</a> <a href="/tps/${tps.id_tps}" style="font-size:13px">Detail TPS</a>
`) `)
@ -142,13 +130,11 @@ function icon(color) {
marker.tpsData = tps; marker.tpsData = tps;
markers.push(marker); markers.push(marker);
}); });
/* ============================= /* ===== LEGEND KATEGORI (BALIK 😤) ===== */
LEGEND var legend = L.control({ position: 'bottomleft' });
============================= */ legend.onAdd = function () {
var legend = L.control({ position: 'bottomleft' });
legend.onAdd = function () {
var div = L.DomUtil.create('div', 'legend'); var div = L.DomUtil.create('div', 'legend');
div.innerHTML = ` div.innerHTML = `
<strong>Kategori TPS</strong><br> <strong>Kategori TPS</strong><br>
@ -157,28 +143,22 @@ function icon(color) {
<i style="background:#dc3545"></i> TPA <i style="background:#dc3545"></i> TPA
`; `;
return div; return div;
}; };
legend.addTo(map); legend.addTo(map);
/* ============================= function getDistance(lat1, lon1, lat2, lon2) {
HITUNG JARAK
============================= */
function getDistance(lat1, lon1, lat2, lon2) {
const R = 6371; const R = 6371;
const dLat = (lat2-lat1) * Math.PI/180; const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2-lon1) * Math.PI/180; const dLon = (lon2 - lon1) * Math.PI / 180;
const a = const a =
Math.sin(dLat/2)**2 + Math.sin(dLat / 2) ** 2 +
Math.cos(lat1*Math.PI/180) * Math.cos(lat1 * Math.PI / 180) *
Math.cos(lat2*Math.PI/180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon/2)**2; Math.sin(dLon / 2) ** 2;
return R * (2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))); return R * (2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)));
} }
/* ============================= document.getElementById('btnLokasi').addEventListener('click', function () {
TPS TERDEKAT
============================= */
document.getElementById('btnLokasi').addEventListener('click', function () {
if (!navigator.geolocation) { if (!navigator.geolocation) {
alert('Browser tidak mendukung GPS'); alert('Browser tidak mendukung GPS');
@ -189,27 +169,37 @@ function getDistance(lat1, lon1, lat2, lon2) {
let userLat = pos.coords.latitude; let userLat = pos.coords.latitude;
let userLng = pos.coords.longitude; let userLng = pos.coords.longitude;
// LINGKARAN BIRU (USER)
L.circleMarker([userLat, userLng], { L.circleMarker([userLat, userLng], {
radius: 8, radius: 6,
color: '#0d6efd', color: '#0d6efd',
fillColor: '#0d6efd', fillColor: '#0d6efd',
fillOpacity: 0.8 fillOpacity: 1,
className: 'user-location'
}).addTo(map) }).addTo(map)
.bindPopup('Lokasi Anda') .bindPopup('Lokasi Anda')
.openPopup(); .openPopup();
setInterval(() => {
let pulse = L.circle([userLat, userLng], {
radius: 40,
color: '#0d6efd',
fillColor: '#0d6efd',
fillOpacity: 0.35,
weight: 0
}).addTo(map);
setTimeout(() => map.removeLayer(pulse), 1000);
}, 1200);
let nearest = null; let nearest = null;
let minDist = Infinity; let minDist = Infinity;
markers.forEach(m => { markers.forEach(m => {
let tps = m.tpsData; let tps = m.tpsData;
let dist = getDistance(userLat, userLng, tps.latitude, tps.longitude); let dist = getDistance(userLat, userLng, tps.latitude, tps.longitude);
if (dist < minDist) { if (dist < minDist) {
minDist = dist; minDist = dist;
nearest = m; nearest = m;
nearest.distance = dist;
} }
}); });
@ -219,6 +209,6 @@ function getDistance(lat1, lon1, lat2, lon2) {
map.setView(nearest.getLatLng(), 15); map.setView(nearest.getLatLng(), 15);
} }
}); });
}); });
</script> </script>
@endsection @endsection