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