332 lines
12 KiB
PHP
332 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Traits;
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
trait LocationTrait
|
|
{
|
|
/**
|
|
* Get the profile's location and optionally generate a link to OpenStreetMap.
|
|
* Attention: do not extensively use the $lookUpOsmLocation as too many request will be rate-limited!
|
|
*
|
|
* @param bool $lookUpOsmLocation Whether to fetch OSM coordinates, URL and polygon data
|
|
* @return array
|
|
*/
|
|
public function getLocationFirst($lookUpOsmLocation = false)
|
|
{
|
|
// Initialize variables
|
|
$location = '';
|
|
$country = '';
|
|
$division = '';
|
|
$city = '';
|
|
$district = '';
|
|
$locationData = [];
|
|
|
|
if ($this->locations && $this->locations->isNotEmpty()) {
|
|
// Use the first location if available
|
|
$firstLocation = $this->locations->first();
|
|
} else {
|
|
// Fallback to the single location property
|
|
$firstLocation = $this->location;
|
|
}
|
|
|
|
if ($firstLocation) {
|
|
if (isset($firstLocation->city)) {
|
|
$cityTranslation = $firstLocation->city->translations->first();
|
|
$city = $cityTranslation ? $cityTranslation->name : '';
|
|
$location = $city;
|
|
}
|
|
if (isset($firstLocation->district)) {
|
|
$districtTranslation = $firstLocation->district->translations->first();
|
|
$district = $districtTranslation ? $districtTranslation->name : '';
|
|
$location = $city ? $city . ' ' . $district : $district;
|
|
}
|
|
if (isset($firstLocation->division)) {
|
|
$divisionTranslation = $firstLocation->division->translations->first();
|
|
$division = $divisionTranslation ? $divisionTranslation->name : '';
|
|
$location = $city || $district ? $location . ', ' . $division : $division;
|
|
}
|
|
if (isset($firstLocation->country)) {
|
|
if ($firstLocation->country->code === 'XX') {
|
|
$country = __('Location not specified');
|
|
$countryName = $country;
|
|
} else {
|
|
$country = $firstLocation->country->code;
|
|
$countryName = $firstLocation->country->translations->first()->name;
|
|
}
|
|
$location = $city || $district || $division ? $location . ', ' . $country : $country;
|
|
}
|
|
}
|
|
|
|
// Remove trailing comma and space
|
|
$locationName = rtrim($location, ', ');
|
|
|
|
// Remove any space before a comma
|
|
$locationName = preg_replace('/\s+,/', ',', $locationName);
|
|
|
|
$locationData['name'] = $locationName;
|
|
|
|
// Construct name_short based on available properties
|
|
$nameShortParts = [];
|
|
|
|
|
|
if ($city) {
|
|
$nameShortParts[] = $city;
|
|
}
|
|
if ($district && count($nameShortParts) < 2) {
|
|
$nameShortParts[] = $district;
|
|
}
|
|
if ($division && count($nameShortParts) < 2) {
|
|
$nameShortParts[] = $division;
|
|
}
|
|
if (!$city && $country && count($nameShortParts) < 2) {
|
|
$nameShortParts[] = $countryName;
|
|
}
|
|
|
|
|
|
// Join the parts with a space
|
|
$locationData['name_short'] = implode(' ', $nameShortParts);
|
|
|
|
// Remove any space before a comma
|
|
$locationData['name_short'] = preg_replace('/\s+,/', ',', $locationData['name_short']);
|
|
|
|
if ($lookUpOsmLocation == true) {
|
|
// Construct the URL for Nominatim search with polygon data
|
|
$searchUrl = 'https://nominatim.openstreetmap.org/search?format=json&polygon_geojson=1&q=' . urlencode($locationName);
|
|
|
|
// Define your User-Agent
|
|
$userAgent = config('app.name') . ' (' . timebank_config('mail.system_admin.email') . ')';
|
|
|
|
// Send the HTTP request to the Nominatim API
|
|
$response = Http::withHeaders([
|
|
'User-Agent' => $userAgent,
|
|
])->get($searchUrl);
|
|
|
|
// Parse the JSON response
|
|
$data = $response->json();
|
|
|
|
// Extract the first result's coordinates (if available)
|
|
if (!empty($data[0])) {
|
|
$latitude = $data[0]['lat'];
|
|
$longitude = $data[0]['lon'];
|
|
|
|
// Add coordinate data
|
|
$locationData['latitude'] = $latitude;
|
|
$locationData['longitude'] = $longitude;
|
|
|
|
// Add polygon data if available
|
|
if (isset($data[0]['geojson'])) {
|
|
$locationData['polygon'] = $data[0]['geojson'];
|
|
$locationData['polygon_type'] = $data[0]['geojson']['type'] ?? null;
|
|
|
|
// Create OpenStreetMap URL using polygon data (relation/way)
|
|
if (isset($data[0]['osm_type']) && isset($data[0]['osm_id'])) {
|
|
$osmType = $data[0]['osm_type'];
|
|
$osmId = $data[0]['osm_id'];
|
|
|
|
// Use relation or way URL for default polygon display with sidebar
|
|
if ($osmType === 'relation') {
|
|
$locationData['url'] = "https://www.openstreetmap.org/relation/{$osmId}";
|
|
} elseif ($osmType === 'way') {
|
|
$locationData['url'] = "https://www.openstreetmap.org/way/{$osmId}";
|
|
} else {
|
|
// Fallback to coordinate-based URL with center marker
|
|
$locationData['url'] = "https://www.openstreetmap.org/?mlat={$latitude}&mlon={$longitude}#map=12/{$latitude}/{$longitude}&layers=V";
|
|
}
|
|
} else {
|
|
// Fallback to coordinate-based URL with center marker
|
|
$locationData['url'] = "https://www.openstreetmap.org/?mlat={$latitude}&mlon={$longitude}#map=12/{$latitude}/{$longitude}&layers=V";
|
|
}
|
|
} else {
|
|
// No polygon data, use coordinate-based URL with center marker
|
|
$locationData['url'] = "https://www.openstreetmap.org/?mlat={$latitude}&mlon={$longitude}#map=12/{$latitude}/{$longitude}&layers=V";
|
|
}
|
|
|
|
// Add place information
|
|
$locationData['place_id'] = $data[0]['place_id'] ?? null;
|
|
$locationData['osm_type'] = $data[0]['osm_type'] ?? null;
|
|
$locationData['osm_id'] = $data[0]['osm_id'] ?? null;
|
|
$locationData['display_name'] = $data[0]['display_name'] ?? null;
|
|
} else {
|
|
$locationData['url'] = null; // No location found
|
|
}
|
|
}
|
|
|
|
return $locationData;
|
|
}
|
|
|
|
|
|
private function getOsmUrl($locationName)
|
|
{
|
|
$searchUrl = 'https://nominatim.openstreetmap.org/search?format=json&q=' . urlencode($locationName);
|
|
|
|
// Define your User-Agent
|
|
$userAgent = config('app.name') . ' (' . timebank_config('mail.system_admin.email') . ')';
|
|
|
|
// Send the HTTP request to the Nominatim API
|
|
$response = Http::withHeaders([
|
|
'User-Agent' => $userAgent,
|
|
])->get($searchUrl);
|
|
|
|
// Parse the JSON response
|
|
$data = $response->json();
|
|
|
|
// Extract the first result's coordinates (if available)
|
|
if (!empty($data[0])) {
|
|
$latitude = $data[0]['lat'];
|
|
$longitude = $data[0]['lon'];
|
|
|
|
// Create the OpenStreetMap URL with the coordinates
|
|
$locationData['url'] = "https://www.openstreetmap.org/?mlat={$latitude}&mlon={$longitude}#map=12/{$latitude}/{$longitude}";
|
|
} else {
|
|
$locationData['url'] = null; // No location found
|
|
}
|
|
|
|
|
|
return $locationData;
|
|
}
|
|
|
|
/**
|
|
* Get polygon boundary for a specific location
|
|
*
|
|
* @param string $locationName The location to search for
|
|
* @param array $options Additional search options (type, limit, etc.)
|
|
* @return array|null Returns polygon data or null if not found
|
|
*/
|
|
public function getLocationPolygon($locationName, $options = [])
|
|
{
|
|
// Default options
|
|
$defaultOptions = [
|
|
'type' => null, // e.g., 'city', 'administrative', 'boundary'
|
|
'limit' => 1,
|
|
'admin_level' => null, // e.g., 8 for cities, 4 for states
|
|
];
|
|
|
|
$options = array_merge($defaultOptions, $options);
|
|
|
|
// Construct the URL for Nominatim search
|
|
$searchUrl = 'https://nominatim.openstreetmap.org/search?format=json&polygon_geojson=1&q=' . urlencode($locationName);
|
|
|
|
// Add optional filters
|
|
if ($options['limit']) {
|
|
$searchUrl .= '&limit=' . $options['limit'];
|
|
}
|
|
|
|
// Define your User-Agent
|
|
$userAgent = config('app.name') . ' (' . timebank_config('mail.system_admin.email') . ')';
|
|
|
|
try {
|
|
// Send the HTTP request to the Nominatim API
|
|
$response = Http::timeout(10)->withHeaders([
|
|
'User-Agent' => $userAgent,
|
|
])->get($searchUrl);
|
|
|
|
// Parse the JSON response
|
|
$data = $response->json();
|
|
|
|
if (empty($data)) {
|
|
return null;
|
|
}
|
|
|
|
// Filter results by type if specified
|
|
if ($options['type']) {
|
|
$data = array_filter($data, function($item) use ($options) {
|
|
return isset($item['type']) && $item['type'] === $options['type'];
|
|
});
|
|
}
|
|
|
|
// Filter by admin level if specified
|
|
if ($options['admin_level']) {
|
|
$data = array_filter($data, function($item) use ($options) {
|
|
return isset($item['admin_level']) && $item['admin_level'] == $options['admin_level'];
|
|
});
|
|
}
|
|
|
|
// Get the first (best) result
|
|
$result = reset($data);
|
|
|
|
if (!$result || !isset($result['geojson'])) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'polygon' => $result['geojson'],
|
|
'polygon_type' => $result['geojson']['type'] ?? null,
|
|
'coordinates_count' => $this->countPolygonCoordinates($result['geojson']),
|
|
'latitude' => $result['lat'] ?? null,
|
|
'longitude' => $result['lon'] ?? null,
|
|
'display_name' => $result['display_name'] ?? null,
|
|
'place_id' => $result['place_id'] ?? null,
|
|
'osm_type' => $result['osm_type'] ?? null,
|
|
'osm_id' => $result['osm_id'] ?? null,
|
|
'boundingbox' => $result['boundingbox'] ?? null,
|
|
];
|
|
|
|
} catch (\Exception $e) {
|
|
// Log error if needed
|
|
\Log::warning('Nominatim polygon request failed: ' . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Count the number of coordinates in a GeoJSON polygon
|
|
* Useful for determining polygon complexity
|
|
*
|
|
* @param array $geojson
|
|
* @return int
|
|
*/
|
|
private function countPolygonCoordinates($geojson)
|
|
{
|
|
if (!isset($geojson['coordinates'])) {
|
|
return 0;
|
|
}
|
|
|
|
$count = 0;
|
|
if ($geojson['type'] === 'Polygon') {
|
|
foreach ($geojson['coordinates'] as $ring) {
|
|
$count += count($ring);
|
|
}
|
|
} elseif ($geojson['type'] === 'MultiPolygon') {
|
|
foreach ($geojson['coordinates'] as $polygon) {
|
|
foreach ($polygon as $ring) {
|
|
$count += count($ring);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* Get simplified polygon for display purposes
|
|
* Nominatim returns detailed polygons that might be too complex for some uses
|
|
*
|
|
* @param string $locationName
|
|
* @return array|null
|
|
*/
|
|
public function getSimplifiedLocationPolygon($locationName)
|
|
{
|
|
$polygon = $this->getLocationPolygon($locationName);
|
|
|
|
if (!$polygon || !$polygon['polygon']) {
|
|
return null;
|
|
}
|
|
|
|
// If polygon has too many coordinates, you might want to simplify it
|
|
// This is a basic example - you might want to use a proper simplification algorithm
|
|
if ($polygon['coordinates_count'] > 1000) {
|
|
// For very complex polygons, you might want to:
|
|
// 1. Use a different endpoint
|
|
// 2. Implement coordinate simplification
|
|
// 3. Use bounding box instead
|
|
|
|
$polygon['simplified'] = true;
|
|
$polygon['original_coordinates_count'] = $polygon['coordinates_count'];
|
|
}
|
|
|
|
return $polygon;
|
|
}
|
|
}
|