113 lines
3.3 KiB
PHP
113 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Http\Client\ConnectionException;
|
|
use Illuminate\Http\Client\PendingRequest;
|
|
use Illuminate\Http\Client\RequestException;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class GeocodingService
|
|
{
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function search(string $query): array
|
|
{
|
|
$query = trim($query);
|
|
|
|
if (strlen($query) < 3) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
$response = $this->client()->get(config('services.nominatim.url').'/search', [
|
|
'q' => $query,
|
|
'format' => 'json',
|
|
'addressdetails' => 1,
|
|
'limit' => 5,
|
|
'countrycodes' => 'fr',
|
|
]);
|
|
} catch (ConnectionException|RequestException $exception) {
|
|
Log::warning('Geocoding search failed.', [
|
|
'query' => $query,
|
|
'message' => $exception->getMessage(),
|
|
]);
|
|
|
|
return [];
|
|
}
|
|
|
|
if (! $response->successful()) {
|
|
return [];
|
|
}
|
|
|
|
return collect($response->json())
|
|
->map(fn (array $item) => $this->formatResult($item))
|
|
->filter(fn (array $item) => $item['address'] !== '')
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @return array{latitude: float, longitude: float}|null
|
|
*/
|
|
public function geocode(string $address, ?string $city = null, ?string $zipCode = null): ?array
|
|
{
|
|
$query = collect([$address, $zipCode, $city, 'France'])
|
|
->filter(fn (?string $part) => $part !== null && trim($part) !== '')
|
|
->implode(', ');
|
|
|
|
$results = $this->search($query);
|
|
|
|
if ($results === [] || $results[0]['latitude'] === null || $results[0]['longitude'] === null) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'latitude' => $results[0]['latitude'],
|
|
'longitude' => $results[0]['longitude'],
|
|
];
|
|
}
|
|
|
|
private function client(): PendingRequest
|
|
{
|
|
return Http::withHeaders([
|
|
'User-Agent' => config('services.nominatim.user_agent'),
|
|
'Accept-Language' => 'fr',
|
|
])
|
|
->withOptions([
|
|
'verify' => (bool) config('services.nominatim.verify_ssl', true),
|
|
])
|
|
->timeout(8)
|
|
->connectTimeout(4);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $item
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function formatResult(array $item): array
|
|
{
|
|
$addressParts = $item['address'] ?? [];
|
|
$street = trim(($addressParts['house_number'] ?? '').' '.($addressParts['road'] ?? ''));
|
|
|
|
if ($street === '') {
|
|
$street = trim((string) ($item['name'] ?? ''));
|
|
}
|
|
|
|
return [
|
|
'label' => (string) ($item['display_name'] ?? $street),
|
|
'address' => $street,
|
|
'city' => $addressParts['city']
|
|
?? $addressParts['town']
|
|
?? $addressParts['village']
|
|
?? $addressParts['municipality']
|
|
?? '',
|
|
'zip_code' => (string) ($addressParts['postcode'] ?? ''),
|
|
'latitude' => isset($item['lat']) ? (float) $item['lat'] : null,
|
|
'longitude' => isset($item['lon']) ? (float) $item['lon'] : null,
|
|
];
|
|
}
|
|
}
|