> */ 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 $item * @return array */ 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, ]; } }