first commit
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class OpenTripMapService
|
||||
{
|
||||
private string $baseUrl;
|
||||
private string $apiKey;
|
||||
private int $cacheTtl;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->baseUrl = 'https://api.opentripmap.com/0.1/en/places';
|
||||
$this->apiKey = config('services.opentripmap.key', env('OPENTRIPMAP_API_KEY'));
|
||||
$this->cacheTtl = config('services.opentripmap.cache_ttl', 7) * 24 * 60; // Convert days to minutes
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attractions near coordinates
|
||||
*
|
||||
* @param float $lat
|
||||
* @param float $lon
|
||||
* @param int $radius in meters (default 5000m = 5km)
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function getAttractions(float $lat, float $lon, int $radius = 5000, int $limit = 50): array
|
||||
{
|
||||
$cacheKey = "opentripmap:attractions:{$lat}:{$lon}:{$radius}:{$limit}";
|
||||
|
||||
return Cache::remember($cacheKey, $this->cacheTtl, function () use ($lat, $lon, $radius, $limit) {
|
||||
try {
|
||||
$response = Http::timeout(15)
|
||||
->get("{$this->baseUrl}/radius", [
|
||||
'apikey' => $this->apiKey,
|
||||
'radius' => $radius,
|
||||
'lon' => $lon,
|
||||
'lat' => $lat,
|
||||
'limit' => $limit,
|
||||
'format' => 'json',
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
$features = $response->json('features', []);
|
||||
$attractions = [];
|
||||
|
||||
foreach ($features as $feature) {
|
||||
$properties = $feature['properties'] ?? [];
|
||||
$geometry = $feature['geometry'] ?? [];
|
||||
$coordinates = $geometry['coordinates'] ?? [];
|
||||
|
||||
if (!empty($properties['name'])) {
|
||||
$attractions[] = [
|
||||
'name' => $properties['name'],
|
||||
'source_id' => $properties['xid'] ?? null,
|
||||
'latitude' => $coordinates[1] ?? null,
|
||||
'longitude' => $coordinates[0] ?? null,
|
||||
'category' => $this->mapCategory($properties['kinds'] ?? ''),
|
||||
'rating' => $properties['rate'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $attractions;
|
||||
}
|
||||
|
||||
Log::warning("OpenTripMap API request failed", [
|
||||
'status' => $response->status(),
|
||||
'lat' => $lat,
|
||||
'lon' => $lon,
|
||||
]);
|
||||
|
||||
return [];
|
||||
} catch (\Exception $e) {
|
||||
Log::error("OpenTripMap API error", [
|
||||
'error' => $e->getMessage(),
|
||||
'lat' => $lat,
|
||||
'lon' => $lon,
|
||||
]);
|
||||
|
||||
return [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed information about an attraction
|
||||
*
|
||||
* @param string $xid OpenTripMap attraction ID
|
||||
* @return array|null
|
||||
*/
|
||||
public function getAttractionDetails(string $xid): ?array
|
||||
{
|
||||
$cacheKey = "opentripmap:details:{$xid}";
|
||||
|
||||
return Cache::remember($cacheKey, $this->cacheTtl, function () use ($xid) {
|
||||
try {
|
||||
$response = Http::timeout(10)
|
||||
->get("{$this->baseUrl}/xid/{$xid}", [
|
||||
'apikey' => $this->apiKey,
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
return [
|
||||
'name' => $data['name'] ?? null,
|
||||
'description' => $data['wikipedia_extracts']['text'] ?? $data['info']['descr'] ?? null,
|
||||
'image_url' => $data['preview']['source'] ?? $data['image'] ?? null,
|
||||
'wikipedia_url' => $data['wikipedia'] ?? null,
|
||||
'rating' => $data['rate'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (\Exception $e) {
|
||||
Log::error("OpenTripMap details error for xid: {$xid}", [
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Map OpenTripMap categories to our simplified categories
|
||||
*
|
||||
* @param string $kinds
|
||||
* @return string
|
||||
*/
|
||||
private function mapCategory(string $kinds): string
|
||||
{
|
||||
$kinds = strtolower($kinds);
|
||||
|
||||
if (str_contains($kinds, 'museums')) {
|
||||
return 'museum';
|
||||
} elseif (str_contains($kinds, 'theatres_and_entertainments') || str_contains($kinds, 'cinemas')) {
|
||||
return 'entertainment';
|
||||
} elseif (str_contains($kinds, 'cultural') || str_contains($kinds, 'interesting_places')) {
|
||||
return 'cultural';
|
||||
} elseif (str_contains($kinds, 'architecture') || str_contains($kinds, 'historic')) {
|
||||
return 'historic';
|
||||
} elseif (str_contains($kinds, 'natural') || str_contains($kinds, 'nature_reserves')) {
|
||||
return 'nature';
|
||||
} elseif (str_contains($kinds, 'sport')) {
|
||||
return 'sport';
|
||||
} elseif (str_contains($kinds, 'food') || str_contains($kinds, 'restaurants')) {
|
||||
return 'restaurant';
|
||||
}
|
||||
|
||||
return 'other';
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache for specific coordinates
|
||||
*
|
||||
* @param float $lat
|
||||
* @param float $lon
|
||||
* @return void
|
||||
*/
|
||||
public function clearCache(float $lat, float $lon): void
|
||||
{
|
||||
// Clear all related cache keys
|
||||
Cache::forget("opentripmap:attractions:{$lat}:{$lon}:5000:50");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
<?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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class WikipediaService
|
||||
{
|
||||
private string $baseUrl;
|
||||
private int $cacheTtl;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->baseUrl = config('services.wikipedia.url', 'https://en.wikipedia.org/api/rest_v1');
|
||||
$this->cacheTtl = config('services.wikipedia.cache_ttl', 30) * 24 * 60; // Convert days to minutes
|
||||
}
|
||||
|
||||
/**
|
||||
* Get city summary from Wikipedia
|
||||
*
|
||||
* @param string $cityName
|
||||
* @return array|null
|
||||
*/
|
||||
public function getCitySummary(string $cityName): ?array
|
||||
{
|
||||
$cacheKey = "wikipedia:summary:{$cityName}";
|
||||
|
||||
return Cache::remember($cacheKey, $this->cacheTtl, function () use ($cityName) {
|
||||
try {
|
||||
$response = Http::withHeaders([
|
||||
'User-Agent' => 'WikiTravelApp/1.0 (contact@example.com)'
|
||||
])
|
||||
->timeout(10)
|
||||
->get("{$this->baseUrl}/page/summary/" . urlencode($cityName));
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
Log::info("Wiki message ". json_encode($data));
|
||||
|
||||
return [
|
||||
'title' => $data['title'] ?? null,
|
||||
'country' => $data['description'] ?? null,
|
||||
'summary' => $data['extract'] ?? null,
|
||||
'url' => $data['content_urls']['desktop']['page'] ?? null,
|
||||
'page_id' => $data['pageid'] ?? null,
|
||||
'thumbnail' => $data['thumbnail']['source'] ?? null,
|
||||
'image' => $data['originalimage']['source'] ?? null,
|
||||
'latitude' => $data['coordinates']['lat'] ?? null,
|
||||
'longitude' => $data['coordinates']['lon'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
Log::warning("Wikipedia API request failed for city: {$cityName}", [
|
||||
'status' => $response->status(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Wikipedia API error for city: {$cityName}", [
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for cities on Wikipedia
|
||||
*
|
||||
* @param string $query
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function searchCities(string $query, int $limit = 10): array
|
||||
{
|
||||
try {
|
||||
$response = Http::withHeaders([
|
||||
'User-Agent' => 'WikiTravelApp/1.0 (contact@example.com)'
|
||||
])
|
||||
->timeout(10)
|
||||
->get("{$this->baseUrl}/../w/api.php", [
|
||||
'action' => 'opensearch',
|
||||
'search' => $query,
|
||||
'limit' => $limit,
|
||||
'namespace' => 0,
|
||||
'format' => 'json',
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
$results = [];
|
||||
$titles = $data[1] ?? [];
|
||||
$descriptions = $data[2] ?? [];
|
||||
$urls = $data[3] ?? [];
|
||||
|
||||
foreach ($titles as $index => $title) {
|
||||
$results[] = [
|
||||
'title' => $title,
|
||||
'description' => $descriptions[$index] ?? '',
|
||||
'url' => $urls[$index] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Wikipedia search error for query: {$query}", [
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache for a specific city
|
||||
*
|
||||
* @param string $cityName
|
||||
* @return void
|
||||
*/
|
||||
public function clearCache(string $cityName): void
|
||||
{
|
||||
Cache::forget("wikipedia:summary:{$cityName}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user