diff --git a/app/Http/Controllers/LocationController.php b/app/Http/Controllers/LocationController.php index 7e0a757..d36c0fa 100644 --- a/app/Http/Controllers/LocationController.php +++ b/app/Http/Controllers/LocationController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Services\Location\Geocoder; +use Illuminate\Support\Facades\Log; class LocationController extends Controller { diff --git a/app/Services/Location/Geocoder.php b/app/Services/Location/Geocoder.php index 3d43bae..ad81d39 100644 --- a/app/Services/Location/Geocoder.php +++ b/app/Services/Location/Geocoder.php @@ -4,11 +4,14 @@ namespace App\Services\Location; use \App\Models\User; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; class Geocoder { + private bool $missingKeyWarned = false; + public function __construct(private array $cfg = []) { $this->cfg = config("services.geocoding"); @@ -65,6 +68,31 @@ class Geocoder return rtrim($this->cfg["arcgis"]["endpoint"], "/"); } + /** + * fetch arcgis api key (log once if missing) + */ + private function arcgisKey(): ?string + { + $key = $this->cfg['arcgis']['api_key'] ?? null; + + if (!$key) { + $this->warnMissingKey(); + return null; + } + + return $key; + } + + private function warnMissingKey(): void + { + if ($this->missingKeyWarned) { + return; + } + + $this->missingKeyWarned = true; + Log::warning('arcgis api key missing; geocoding disabled'); + } + /** * pull a bias from the user (zip -> centroid) and cache it */ @@ -85,8 +113,13 @@ class Geocoder return null; } + $key = $this->arcgisKey(); + if (!$key) { + return null; + } + $cacheKey = "geo:bias:zip:{$zip}"; - $bias = Cache::remember($cacheKey, now()->addDays(7), function () use ($zip) { + $bias = Cache::remember($cacheKey, now()->addDays(7), function () use ($zip, $key) { $a = $this->cfg['arcgis']; $params = [ @@ -94,7 +127,7 @@ class Geocoder 'category' => 'Postal', 'maxLocations' => 1, 'f' => 'pjson', - 'token' => $a['api_key'], + 'token' => $key, 'countryCode' => $a['country_code'] ?? null, ]; @@ -139,17 +172,25 @@ class Geocoder private function forwardArcgis(string $query, ?array $bias): ?array { $a = $this->cfg['arcgis']; + $key = $this->arcgisKey(); + if (!$key) { + return null; + } $params = [ 'singleLine' => $query, 'outFields' => $a['out_fields'] ?? '*', 'maxLocations' => (int)($a['max_results'] ?? 5), 'f' => 'pjson', - 'token' => $a['api_key'], + 'token' => $key, 'category' => $a['categories'] ?? 'POI,Address', 'countryCode' => $a['country_code'] ?? null, ]; + if (!empty($a['store'])) { + $params['forStorage'] = 'true'; + } + if ($bias && $bias['lat'] && $bias['lon']) { $params['location'] = $bias['lon'].','.$bias['lat']; if (!empty($bias['radius_km'])) { @@ -181,12 +222,21 @@ class Geocoder private function reverseArcgis(float $lat, float $lon): ?array { $a = $this->cfg['arcgis']; + $key = $this->arcgisKey(); + if (!$key) { + return null; + } + $params = [ 'location' => "{$lon},{$lat}", 'f' => 'pjson', - 'token' => $a['api_key'], + 'token' => $key, ]; + if (!empty($a['store'])) { + $params['forStorage'] = 'true'; + } + $res = $this->http()->get($this->arcgisBase().'/reverseGeocode', $params); if (!$res->ok()) { Log::warning('arcgis reverse geocode failed', ['status' => $res->status()]); @@ -247,8 +297,10 @@ class Geocoder return []; } + $bias = $this->biasForUser($user); + return match ($provider) { - "arcgis" => $this->arcgisSuggestions($query, $limit), + "arcgis" => $this->arcgisSuggestions($query, $limit, $bias), default => [], }; } @@ -256,17 +308,34 @@ class Geocoder /** * get the suggestions from arcgis */ - private function arcgisSuggestions(string $query, int $limit): array + private function arcgisSuggestions(string $query, int $limit, ?array $bias = null): array { + $key = $this->arcgisKey(); + if (!$key) { + return []; + } + $params = [ "singleLine" => $query, "outFields" => $this->cfg["arcgis"]["out_fields"] ?? "*", "maxLocations" => $limit, - // you can bias results with 'countryCode' or 'location' here if desired + "category" => $this->cfg["arcgis"]["categories"] ?? "POI,Address", + "countryCode" => $this->cfg["arcgis"]["country_code"] ?? null, "f" => "pjson", - "token" => $this->cfg["arcgis"]["api_key"], + "token" => $key, ]; + if ($bias && $bias['lat'] && $bias['lon']) { + $params['location'] = $bias['lon'] . ',' . $bias['lat']; + if (!empty($bias['radius_km'])) { + $params['searchExtent'] = $this->bboxFromBias( + (float) $bias['lat'], + (float) $bias['lon'], + (float) $bias['radius_km'] + ); + } + } + if (!empty($this->cfg["arcgis"]["store"])) { $params["forStorage"] = "true"; }