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"); } }