Guard added for ArcGIS api key; ArcGIS issues in LocationController and Geocoder fixed

This commit is contained in:
Andrew Gioia 2026-02-05 13:15:18 -05:00
parent 07399d7d45
commit 5563826a08
Signed by: andrew
GPG Key ID: FC09694A000800C8
2 changed files with 78 additions and 8 deletions

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Services\Location\Geocoder; use App\Services\Location\Geocoder;
use Illuminate\Support\Facades\Log;
class LocationController extends Controller class LocationController extends Controller
{ {

View File

@ -4,11 +4,14 @@ namespace App\Services\Location;
use \App\Models\User; use \App\Models\User;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class Geocoder class Geocoder
{ {
private bool $missingKeyWarned = false;
public function __construct(private array $cfg = []) public function __construct(private array $cfg = [])
{ {
$this->cfg = config("services.geocoding"); $this->cfg = config("services.geocoding");
@ -65,6 +68,31 @@ class Geocoder
return rtrim($this->cfg["arcgis"]["endpoint"], "/"); 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 * pull a bias from the user (zip -> centroid) and cache it
*/ */
@ -85,8 +113,13 @@ class Geocoder
return null; return null;
} }
$key = $this->arcgisKey();
if (!$key) {
return null;
}
$cacheKey = "geo:bias:zip:{$zip}"; $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']; $a = $this->cfg['arcgis'];
$params = [ $params = [
@ -94,7 +127,7 @@ class Geocoder
'category' => 'Postal', 'category' => 'Postal',
'maxLocations' => 1, 'maxLocations' => 1,
'f' => 'pjson', 'f' => 'pjson',
'token' => $a['api_key'], 'token' => $key,
'countryCode' => $a['country_code'] ?? null, 'countryCode' => $a['country_code'] ?? null,
]; ];
@ -139,17 +172,25 @@ class Geocoder
private function forwardArcgis(string $query, ?array $bias): ?array private function forwardArcgis(string $query, ?array $bias): ?array
{ {
$a = $this->cfg['arcgis']; $a = $this->cfg['arcgis'];
$key = $this->arcgisKey();
if (!$key) {
return null;
}
$params = [ $params = [
'singleLine' => $query, 'singleLine' => $query,
'outFields' => $a['out_fields'] ?? '*', 'outFields' => $a['out_fields'] ?? '*',
'maxLocations' => (int)($a['max_results'] ?? 5), 'maxLocations' => (int)($a['max_results'] ?? 5),
'f' => 'pjson', 'f' => 'pjson',
'token' => $a['api_key'], 'token' => $key,
'category' => $a['categories'] ?? 'POI,Address', 'category' => $a['categories'] ?? 'POI,Address',
'countryCode' => $a['country_code'] ?? null, 'countryCode' => $a['country_code'] ?? null,
]; ];
if (!empty($a['store'])) {
$params['forStorage'] = 'true';
}
if ($bias && $bias['lat'] && $bias['lon']) { if ($bias && $bias['lat'] && $bias['lon']) {
$params['location'] = $bias['lon'].','.$bias['lat']; $params['location'] = $bias['lon'].','.$bias['lat'];
if (!empty($bias['radius_km'])) { if (!empty($bias['radius_km'])) {
@ -181,12 +222,21 @@ class Geocoder
private function reverseArcgis(float $lat, float $lon): ?array private function reverseArcgis(float $lat, float $lon): ?array
{ {
$a = $this->cfg['arcgis']; $a = $this->cfg['arcgis'];
$key = $this->arcgisKey();
if (!$key) {
return null;
}
$params = [ $params = [
'location' => "{$lon},{$lat}", 'location' => "{$lon},{$lat}",
'f' => 'pjson', 'f' => 'pjson',
'token' => $a['api_key'], 'token' => $key,
]; ];
if (!empty($a['store'])) {
$params['forStorage'] = 'true';
}
$res = $this->http()->get($this->arcgisBase().'/reverseGeocode', $params); $res = $this->http()->get($this->arcgisBase().'/reverseGeocode', $params);
if (!$res->ok()) { if (!$res->ok()) {
Log::warning('arcgis reverse geocode failed', ['status' => $res->status()]); Log::warning('arcgis reverse geocode failed', ['status' => $res->status()]);
@ -247,8 +297,10 @@ class Geocoder
return []; return [];
} }
$bias = $this->biasForUser($user);
return match ($provider) { return match ($provider) {
"arcgis" => $this->arcgisSuggestions($query, $limit), "arcgis" => $this->arcgisSuggestions($query, $limit, $bias),
default => [], default => [],
}; };
} }
@ -256,17 +308,34 @@ class Geocoder
/** /**
* get the suggestions from arcgis * 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 = [ $params = [
"singleLine" => $query, "singleLine" => $query,
"outFields" => $this->cfg["arcgis"]["out_fields"] ?? "*", "outFields" => $this->cfg["arcgis"]["out_fields"] ?? "*",
"maxLocations" => $limit, "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", "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"])) { if (!empty($this->cfg["arcgis"]["store"])) {
$params["forStorage"] = "true"; $params["forStorage"] = "true";
} }