Initial commit
This commit is contained in:
331
app/Traits/LocationTrait.php
Normal file
331
app/Traits/LocationTrait.php
Normal file
@@ -0,0 +1,331 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user