222 lines
7.4 KiB
PHP
222 lines
7.4 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Services;
|
||
|
|
|
||
|
|
use Illuminate\Support\Facades\Http;
|
||
|
|
use Illuminate\Support\Facades\Cache;
|
||
|
|
use Illuminate\Support\Facades\Log;
|
||
|
|
|
||
|
|
class OverpassService
|
||
|
|
{
|
||
|
|
private string $baseUrl;
|
||
|
|
private int $cacheTtl;
|
||
|
|
|
||
|
|
public function __construct()
|
||
|
|
{
|
||
|
|
$this->baseUrl = config('services.overpass.url', 'https://overpass-api.de/api/interpreter');
|
||
|
|
$this->cacheTtl = config('services.overpass.cache_ttl', 7) * 24 * 60; // Convert days to minutes
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get points of interest near coordinates
|
||
|
|
*
|
||
|
|
* @param float $lat
|
||
|
|
* @param float $lon
|
||
|
|
* @param int $radius in meters (default 5000m = 5km)
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function getPointsOfInterest(float $lat, float $lon, int $radius = 5000): array
|
||
|
|
{
|
||
|
|
$cacheKey = "overpass:poi:{$lat}:{$lon}:{$radius}";
|
||
|
|
|
||
|
|
return Cache::remember($cacheKey, $this->cacheTtl, function () use ($lat, $lon, $radius) {
|
||
|
|
try {
|
||
|
|
// Overpass QL query for tourism and amenity nodes
|
||
|
|
$query = "[out:json][timeout:25];
|
||
|
|
(
|
||
|
|
node[\"tourism\"~\"attraction|museum|artwork|viewpoint\"](around:{$radius},{$lat},{$lon});
|
||
|
|
node[\"amenity\"~\"restaurant|cafe|bar|theatre|cinema\"](around:{$radius},{$lat},{$lon});
|
||
|
|
node[\"historic\"](around:{$radius},{$lat},{$lon});
|
||
|
|
);
|
||
|
|
out body;
|
||
|
|
>;
|
||
|
|
out skel qt;";
|
||
|
|
|
||
|
|
$response = Http::timeout(30)
|
||
|
|
->asForm()
|
||
|
|
->post($this->baseUrl, [
|
||
|
|
'data' => $query,
|
||
|
|
]);
|
||
|
|
|
||
|
|
if ($response->successful()) {
|
||
|
|
$data = $response->json();
|
||
|
|
$elements = $data['elements'] ?? [];
|
||
|
|
$attractions = [];
|
||
|
|
|
||
|
|
foreach ($elements as $element) {
|
||
|
|
if ($element['type'] === 'node' && !empty($element['tags']['name'])) {
|
||
|
|
$tags = $element['tags'];
|
||
|
|
|
||
|
|
$attractions[] = [
|
||
|
|
'name' => $tags['name'],
|
||
|
|
'source_id' => (string) $element['id'],
|
||
|
|
'latitude' => $element['lat'],
|
||
|
|
'longitude' => $element['lon'],
|
||
|
|
'category' => $this->determineCategory($tags),
|
||
|
|
'description' => $tags['description'] ?? null,
|
||
|
|
'website' => $tags['website'] ?? null,
|
||
|
|
'opening_hours' => $tags['opening_hours'] ?? null,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $attractions;
|
||
|
|
}
|
||
|
|
|
||
|
|
Log::warning("Overpass API request failed", [
|
||
|
|
'status' => $response->status(),
|
||
|
|
'lat' => $lat,
|
||
|
|
'lon' => $lon,
|
||
|
|
]);
|
||
|
|
|
||
|
|
return [];
|
||
|
|
} catch (\Exception $e) {
|
||
|
|
Log::error("Overpass API error", [
|
||
|
|
'error' => $e->getMessage(),
|
||
|
|
'lat' => $lat,
|
||
|
|
'lon' => $lon,
|
||
|
|
]);
|
||
|
|
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Determine category from OSM tags
|
||
|
|
*
|
||
|
|
* @param array $tags
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
private function determineCategory(array $tags): string
|
||
|
|
{
|
||
|
|
// Tourism tags
|
||
|
|
if (isset($tags['tourism'])) {
|
||
|
|
return match ($tags['tourism']) {
|
||
|
|
'museum' => 'museum',
|
||
|
|
'artwork', 'gallery' => 'cultural',
|
||
|
|
'attraction', 'viewpoint' => 'attraction',
|
||
|
|
'hotel', 'hostel', 'guest_house' => 'accommodation',
|
||
|
|
default => 'tourism',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// Amenity tags
|
||
|
|
if (isset($tags['amenity'])) {
|
||
|
|
return match ($tags['amenity']) {
|
||
|
|
'restaurant', 'fast_food', 'food_court' => 'restaurant',
|
||
|
|
'cafe', 'bar', 'pub' => 'cafe',
|
||
|
|
'theatre', 'cinema' => 'entertainment',
|
||
|
|
'library' => 'cultural',
|
||
|
|
default => 'amenity',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// Historic tags
|
||
|
|
if (isset($tags['historic'])) {
|
||
|
|
return 'historic';
|
||
|
|
}
|
||
|
|
|
||
|
|
// Leisure tags
|
||
|
|
if (isset($tags['leisure'])) {
|
||
|
|
return match ($tags['leisure']) {
|
||
|
|
'park', 'garden' => 'park',
|
||
|
|
'sports_centre', 'stadium' => 'sport',
|
||
|
|
default => 'leisure',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
return 'other';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get restaurants and cafes near coordinates
|
||
|
|
*
|
||
|
|
* @param float $lat
|
||
|
|
* @param float $lon
|
||
|
|
* @param int $radius
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function getRestaurants(float $lat, float $lon, int $radius = 2000): array
|
||
|
|
{
|
||
|
|
$cacheKey = "overpass:restaurants:{$lat}:{$lon}:{$radius}";
|
||
|
|
|
||
|
|
return Cache::remember($cacheKey, $this->cacheTtl, function () use ($lat, $lon, $radius) {
|
||
|
|
try {
|
||
|
|
$query = "[out:json][timeout:25];
|
||
|
|
(
|
||
|
|
node[\"amenity\"=\"restaurant\"](around:{$radius},{$lat},{$lon});
|
||
|
|
node[\"amenity\"=\"cafe\"](around:{$radius},{$lat},{$lon});
|
||
|
|
node[\"amenity\"=\"bar\"](around:{$radius},{$lat},{$lon});
|
||
|
|
);
|
||
|
|
out body;";
|
||
|
|
|
||
|
|
$response = Http::timeout(30)
|
||
|
|
->asForm()
|
||
|
|
->post($this->baseUrl, [
|
||
|
|
'data' => $query,
|
||
|
|
]);
|
||
|
|
|
||
|
|
if ($response->successful()) {
|
||
|
|
$data = $response->json();
|
||
|
|
$elements = $data['elements'] ?? [];
|
||
|
|
$restaurants = [];
|
||
|
|
|
||
|
|
foreach ($elements as $element) {
|
||
|
|
if ($element['type'] === 'node' && !empty($element['tags']['name'])) {
|
||
|
|
$tags = $element['tags'];
|
||
|
|
|
||
|
|
$restaurants[] = [
|
||
|
|
'name' => $tags['name'],
|
||
|
|
'source_id' => (string) $element['id'],
|
||
|
|
'latitude' => $element['lat'],
|
||
|
|
'longitude' => $element['lon'],
|
||
|
|
'category' => 'restaurant',
|
||
|
|
'cuisine' => $tags['cuisine'] ?? null,
|
||
|
|
'phone' => $tags['phone'] ?? null,
|
||
|
|
'website' => $tags['website'] ?? null,
|
||
|
|
'opening_hours' => $tags['opening_hours'] ?? null,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $restaurants;
|
||
|
|
}
|
||
|
|
|
||
|
|
return [];
|
||
|
|
} catch (\Exception $e) {
|
||
|
|
Log::error("Overpass restaurants error", [
|
||
|
|
'error' => $e->getMessage(),
|
||
|
|
'lat' => $lat,
|
||
|
|
'lon' => $lon,
|
||
|
|
]);
|
||
|
|
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Clear cache for specific coordinates
|
||
|
|
*
|
||
|
|
* @param float $lat
|
||
|
|
* @param float $lon
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public function clearCache(float $lat, float $lon): void
|
||
|
|
{
|
||
|
|
Cache::forget("overpass:poi:{$lat}:{$lon}:5000");
|
||
|
|
Cache::forget("overpass:restaurants:{$lat}:{$lon}:2000");
|
||
|
|
}
|
||
|
|
}
|