// converter.go package main import ( "fmt" "math" "time" ) // ToOWMResponse converts ForecastData to OWMResponse format func (fd *ForecastData) ToOWMResponse(mapper *WeatherMapper) (*OWMResponse, error) { loc, err := time.LoadLocation(fd.Timezone) if err != nil { return nil, fmt.Errorf("loading timezone: %w", err) } // Calculate timezone offset now := time.Now().In(loc) _, offset := now.Zone() // Create response response := &OWMResponse{ Lat: fd.Latitude, Lon: fd.Longitude, Timezone: fd.Timezone, TimezoneOffset: offset, } // Calculate sunrise/sunset for TODAY (not the forecast date) sunCalc := NewSunCalculator(fd.Latitude, fd.Longitude, float64(offset)/3600) sunrise, sunset, err := sunCalc.Calculate(now) if err != nil { // Fallback to approximate values based on location and time of year sunrise = time.Date(now.Year(), now.Month(), now.Day(), 8, 0, 0, 0, loc) sunset = time.Date(now.Year(), now.Month(), now.Day(), 16, 0, 0, 0, loc) } // Convert each forecast point hourly := make([]Current, 0, len(fd.Timestamps)) for i, timestamp := range fd.Timestamps { current, err := fd.convertToCurrent(i, timestamp, loc, sunrise, sunset, mapper) if err != nil { return nil, fmt.Errorf("converting point %d: %w", i, err) } hourly = append(hourly, current) } response.Current = hourly[0] response.Current.Sunrise = sunrise.Unix() response.Current.Sunset = sunset.Unix() response.Hourly = hourly return response, nil } func (fd *ForecastData) convertToCurrent(index int, timestamp int64, loc *time.Location, sunrise, sunset time.Time, mapper *WeatherMapper) (Current, error) { current := Current{ Dt: timestamp, Uvi: 0, // Not available from FMI } // Add ISO 8601 local time forecastTime := time.Unix(timestamp, 0).In(loc) current.LocalTime = forecastTime.Format(time.RFC3339) // Extract temperature if val, err := fd.getFloatValue(index, "Temperature"); err == nil && !math.IsNaN(val) { current.Temp = val } // Extract humidity if val, err := fd.getFloatValue(index, "Humidity"); err == nil && !math.IsNaN(val) { current.Humidity = int(math.Round(val)) } // Extract pressure if val, err := fd.getFloatValue(index, "Pressure"); err == nil && !math.IsNaN(val) { current.Pressure = int(math.Round(val)) } // Extract dew point if val, err := fd.getFloatValue(index, "dewPoint"); err == nil && !math.IsNaN(val) { current.DewPoint = val } // Extract wind speed (convert m/s to km/h) if val, err := fd.getFloatValue(index, "WindSpeedMS"); err == nil && !math.IsNaN(val) { current.WindSpeed = val * 3.6 } // Extract wind direction if val, err := fd.getFloatValue(index, "WindDirection"); err == nil && !math.IsNaN(val) { current.WindDeg = int(math.Round(val)) } // Extract wind gust (convert m/s to km/h) if val, err := fd.getFloatValue(index, "WindGust"); err == nil && !math.IsNaN(val) { current.WindGust = val * 3.6 } // Extract cloud cover if val, err := fd.getFloatValue(index, "TotalCloudCover"); err == nil && !math.IsNaN(val) { current.Clouds = int(math.Round(val)) } // Extract visibility if val, err := fd.getFloatValue(index, "visibility"); err == nil && !math.IsNaN(val) { current.Visibility = int(math.Round(val)) } // Calculate feels-like temperature current.FeelsLike = CalculateFeelsLike(current.Temp, float64(current.Humidity), current.WindSpeed) // Handle precipitation (convert from mm/h) if val, err := fd.getFloatValue(index, "PrecipitationRate"); err == nil && !math.IsNaN(val) && val > 0 { current.Rain = &Rain{OneH: val} } // Map weather symbol if val, err := fd.getFloatValue(index, "WeatherSymbol3"); err == nil && !math.IsNaN(val) { symbol := int(math.Round(val)) // Use the mapper or fallback to the global map var weather Weather if mapper != nil { weather = mapper.Map(symbol, forecastTime, sunrise, sunset) } else { // Fallback to global map if w, ok := FmiToOwm[symbol]; ok { weather = w // Determine day/night isDay := forecastTime.After(sunrise) && forecastTime.Before(sunset) if isDay { weather.Icon += "d" } else { weather.Icon += "n" } } else { weather = Weather{800, "Clear", "clear sky", "01d"} } } current.Weather = []Weather{weather} } else { // Default weather current.Weather = []Weather{{800, "Clear", "clear sky", "01d"}} } return current, nil } func (fd *ForecastData) getFloatValue(rowIndex int, paramName string) (float64, error) { if paramIndex, ok := fd.ParamIndex[paramName]; ok { if rowIndex < len(fd.Values) && paramIndex < len(fd.Values[rowIndex]) { return fd.Values[rowIndex][paramIndex], nil } } return math.NaN(), fmt.Errorf("parameter %s not found or out of bounds", paramName) }