Files
Verde-Web/app/Services/Geo/GeoLocationService.php
admin c8404564fe feat(backend): complete Modules 2 + 3 (geo + user management)
Module 2 — Geographic Data: PSGC tables (regions/provinces/cities/
barangays) with native geometry(polygon|point, 4326) columns; cascading
dropdown endpoints; ST_Contains-based GPS resolution; service-area CRUD
with barangay attach/detach; SamplePsgcSeeder + psgc:import command.

Module 3 — User Management: 5 role-specific profile tables (driver/
helper/scanner/store_partner with verification_status + rejection
reason); profile auto-created on register; admin user CRUD with
filters/pagination, suspend/activate, profile approve/reject;
self-service /me + /me/profile with role-aware validation that blocks
self-approval and resets rejected profiles to pending on resubmit.

69 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:59:56 +08:00

54 lines
1.7 KiB
PHP

<?php
namespace App\Services\Geo;
use App\Models\Barangay;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class GeoLocationService
{
public function __construct(private readonly int $cacheTtlSeconds = 300) {}
/**
* Resolve a (latitude, longitude) pair to the containing barangay using
* MySQL ST_Contains. Returns null if no barangay polygon contains the point
* or if the input is outside the Philippines bounding box.
*/
public function findBarangayByCoordinates(float $latitude, float $longitude): ?Barangay
{
if (! $this->insidePhilippinesBoundingBox($latitude, $longitude)) {
return null;
}
$key = sprintf('geo:resolve:%.6f:%.6f', $latitude, $longitude);
$barangayId = Cache::remember($key, $this->cacheTtlSeconds, function () use ($latitude, $longitude) {
$row = DB::table('barangays')
->whereNull('deleted_at')
->whereNotNull('boundary')
->whereRaw(
'ST_Contains(boundary, ST_SRID(POINT(?, ?), 4326))',
[$longitude, $latitude],
)
->select('id')
->first();
return $row?->id;
});
if (! $barangayId) {
return null;
}
return Barangay::with('cityMunicipality.province.region')->find($barangayId);
}
private function insidePhilippinesBoundingBox(float $latitude, float $longitude): bool
{
// Rough bounds for the Philippines archipelago.
return $latitude >= 4.5 && $latitude <= 21.5
&& $longitude >= 116.0 && $longitude <= 127.0;
}
}