107 lines
3.9 KiB
PHP
107 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Google Places (New) - Nearby Search.
|
|
* Wird als Fallback aufgerufen wenn Overpass-Mirrors alle tot sind.
|
|
* https://developers.google.com/maps/documentation/places/web-service/nearby-search
|
|
*/
|
|
class PlacesService
|
|
{
|
|
/**
|
|
* Map unsere internen Kategorien auf Google-Places-Types.
|
|
* Multi-Type möglich, Google nimmt OR-Logik.
|
|
*/
|
|
protected const CATEGORY_TYPES = [
|
|
'restaurant' => ['restaurant'],
|
|
'cafe' => ['cafe'],
|
|
'bar' => ['bar', 'night_club'],
|
|
'fast_food' => ['fast_food_restaurant', 'meal_takeaway'],
|
|
'pharmacy' => ['pharmacy'],
|
|
'hospital' => ['hospital'],
|
|
'doctor' => ['doctor'],
|
|
'bank' => ['bank', 'atm'],
|
|
'fuel' => ['gas_station'],
|
|
'supermarket' => ['supermarket'],
|
|
'hotel' => ['hotel', 'lodging'],
|
|
'government' => ['city_hall', 'government_office'],
|
|
'police' => ['police'],
|
|
'parking' => ['parking'],
|
|
];
|
|
|
|
public function nearby(string $category, float $lat, float $lng, int $radius = 2000, ?string $cuisine = null): array
|
|
{
|
|
$key = config('services.google.maps_key');
|
|
if (empty($key)) {
|
|
Log::warning('Places: kein Google-Key, Fallback nicht möglich');
|
|
|
|
return [];
|
|
}
|
|
|
|
$types = self::CATEGORY_TYPES[$category] ?? ['restaurant'];
|
|
|
|
try {
|
|
$response = Http::timeout(10)
|
|
->withHeaders([
|
|
'Content-Type' => 'application/json',
|
|
'X-Goog-Api-Key' => $key,
|
|
'X-Goog-FieldMask' => 'places.id,places.displayName,places.formattedAddress,places.location,places.types,places.rating',
|
|
])
|
|
->post('https://places.googleapis.com/v1/places:searchNearby', [
|
|
'includedTypes' => $types,
|
|
'maxResultCount' => 20,
|
|
'languageCode' => 'de',
|
|
'regionCode' => 'AT',
|
|
'locationRestriction' => [
|
|
'circle' => [
|
|
'center' => ['latitude' => $lat, 'longitude' => $lng],
|
|
'radius' => min((float) $radius, 50_000.0),
|
|
],
|
|
],
|
|
]);
|
|
|
|
if (! $response->successful()) {
|
|
Log::warning('Places API HTTP-Fehler', [
|
|
'status' => $response->status(),
|
|
'body' => mb_substr($response->body(), 0, 200),
|
|
]);
|
|
|
|
return [];
|
|
}
|
|
|
|
$places = $response->json('places') ?? [];
|
|
|
|
// Cuisine-Filter clientseitig: Google hat keine cuisine-Tags, aber wir
|
|
// können nach displayName + types filtern (z.B. "steak" enthalten).
|
|
if ($cuisine !== null) {
|
|
$needle = mb_strtolower($cuisine);
|
|
$places = array_values(array_filter($places, function ($p) use ($needle) {
|
|
$name = mb_strtolower($p['displayName']['text'] ?? '');
|
|
|
|
return str_contains($name, $needle) || str_contains($name, str_replace('_', ' ', $needle));
|
|
}));
|
|
}
|
|
|
|
return array_map(function ($p) use ($category) {
|
|
return [
|
|
'id' => 'gp-'.($p['id'] ?? uniqid()),
|
|
'name' => $p['displayName']['text'] ?? '?',
|
|
'address' => $p['formattedAddress'] ?? '',
|
|
'lat' => $p['location']['latitude'] ?? null,
|
|
'lng' => $p['location']['longitude'] ?? null,
|
|
'rating' => $p['rating'] ?? null,
|
|
'category' => $category,
|
|
];
|
|
}, $places);
|
|
} catch (\Throwable $e) {
|
|
Log::warning('Places exception', ['e' => $e->getMessage()]);
|
|
|
|
return [];
|
|
}
|
|
}
|
|
}
|