where('type', 'hourly') ->with('periods') ->orderByDesc('reported_at') ->first(); $latestWeekly = WeatherReport::query() ->where('type', 'weekly') ->with('periods') ->orderByDesc('reported_at') ->first(); $currentPeriod = $latestHourly?->periods->first(); $hourlyForecast = $latestHourly?->periods ->take(6) ->map(fn ($period) => [ 'time' => $period->start_time->format('H:i'), 'hour' => $period->start_time->format('H'), 'temperature' => $period->temperature, 'temperatureUnit' => $period->temperature_unit, 'icon' => $this->mapIconToEmoji($period->short_forecast), 'shortForecast' => $period->short_forecast, 'precipitationProbability' => $period->precipitation_probability, ]); $weeklyForecast = $latestWeekly?->periods ->filter(fn ($period) => $period->is_daytime) ->take(7) ->values() ->map(fn ($period) => [ 'name' => $period->name, 'temperature' => $period->temperature, 'temperatureUnit' => $period->temperature_unit, 'icon' => $this->mapIconToEmoji($period->short_forecast), 'shortForecast' => $period->short_forecast, 'precipitationProbability' => $period->precipitation_probability, 'windSpeed' => $period->wind_speed, 'windDirection' => $period->wind_direction, 'detailedForecast' => $period->detailed_forecast, ]); $background = $this->getBackgroundForForecast( $currentPeriod?->short_forecast, $currentPeriod?->is_daytime ?? true ); return Inertia::render('Weather', [ 'current' => $currentPeriod ? [ 'temperature' => $currentPeriod->temperature, 'temperatureUnit' => $currentPeriod->temperature_unit, 'shortForecast' => $currentPeriod->short_forecast, 'detailedForecast' => $currentPeriod->detailed_forecast, 'windSpeed' => $currentPeriod->wind_speed, 'windDirection' => $currentPeriod->wind_direction, 'humidity' => $currentPeriod->relative_humidity, 'dewpoint' => $currentPeriod->dewpoint_celsius, 'precipitationProbability' => $currentPeriod->precipitation_probability, 'isDaytime' => $currentPeriod->is_daytime, 'icon' => $this->mapIconToEmoji($currentPeriod->short_forecast), ] : null, 'hourlyForecast' => $hourlyForecast ?? collect(), 'weeklyForecast' => $weeklyForecast ?? collect(), 'location' => [ 'latitude' => $latestHourly?->latitude, 'longitude' => $latestHourly?->longitude, 'name' => 'Utah County', ], 'reportedAt' => $latestHourly?->reported_at?->format('M d, Y H:i'), 'background' => $background, ]); } /** * @return array{imageUrl: string|null, licenseHtml: string|null} */ private function getBackgroundForForecast(?string $forecast, bool $isDaytime): array { $folder = $this->mapForecastToFolder($forecast); $timeOfDay = $isDaytime ? 'day' : 'night'; $basePath = storage_path('app/public/backgrounds/'.$folder); if (! File::isDirectory($basePath)) { return ['imageUrl' => null, 'licenseHtml' => null]; } $files = File::files($basePath); $pattern = '/^'.$timeOfDay.'_[a-zA-Z0-9_-]+\.jpg$/'; $imageFiles = collect($files) ->filter(fn ($file) => preg_match($pattern, $file->getFilename())) ->values(); if ($imageFiles->isEmpty()) { $fallbackPattern = '/^(day|night)_[a-zA-Z0-9_-]+\.jpg$/'; $imageFiles = collect($files) ->filter(fn ($file) => preg_match($fallbackPattern, $file->getFilename())) ->values(); } if ($imageFiles->isEmpty()) { return ['imageUrl' => null, 'licenseHtml' => null]; } $selectedImage = $imageFiles->random(); $imageFilename = $selectedImage->getFilename(); $imageUrl = '/storage/backgrounds/'.$folder.'/'.$imageFilename; $licenseFilename = preg_replace('/\.jpg$/', '_license.html', $imageFilename); $licensePath = $basePath.'/'.$licenseFilename; $licenseHtml = null; if (File::exists($licensePath)) { $licenseHtml = trim(File::get($licensePath)); } return [ 'imageUrl' => $imageUrl, 'licenseHtml' => $licenseHtml, ]; } private function mapForecastToFolder(?string $forecast): string { if (! $forecast) { return 'cloudy'; } $forecast = strtolower($forecast); return match (true) { str_contains($forecast, 'thunder') || str_contains($forecast, 'storm') => 'stormy', str_contains($forecast, 'snow') => 'snowy', str_contains($forecast, 'rain') || str_contains($forecast, 'shower') || str_contains($forecast, 'drizzle') => 'rainy', str_contains($forecast, 'wind') => 'windy', str_contains($forecast, 'sunny') || str_contains($forecast, 'clear') => 'clear', str_contains($forecast, 'cloud') || str_contains($forecast, 'overcast') => 'cloudy', str_contains($forecast, 'fog') || str_contains($forecast, 'mist') => 'cloudy', default => 'cloudy', }; } private function mapIconToEmoji(?string $forecast): string { if (! $forecast) { return 'cloud'; } $forecast = strtolower($forecast); return match (true) { str_contains($forecast, 'sunny') || str_contains($forecast, 'clear') => 'sun', str_contains($forecast, 'partly') && str_contains($forecast, 'cloud') => 'cloud-sun', str_contains($forecast, 'mostly cloudy') => 'cloud', str_contains($forecast, 'cloud') => 'cloud', str_contains($forecast, 'rain') && str_contains($forecast, 'snow') => 'cloud-snow', str_contains($forecast, 'snow') => 'cloud-snow', str_contains($forecast, 'rain') || str_contains($forecast, 'shower') => 'cloud-rain', str_contains($forecast, 'thunder') || str_contains($forecast, 'storm') => 'cloud-lightning', str_contains($forecast, 'fog') || str_contains($forecast, 'mist') => 'cloud-fog', str_contains($forecast, 'wind') => 'wind', default => 'cloud', }; } }