// converter.go - Converts ForecastData to OWMResponse 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 firstTime := time.Unix(fd.Timestamps[0], 0).UTC() _, offset := firstTime.In(loc).Zone() // Create response response := &OWMResponse{ Lat: fd.Latitude, Lon: fd.Longitude, Timezone: fd.Timezone, TimezoneOffset: offset, } // Calculate sunrise/sunset for first day sunCalc := NewSunCalculator(fd.Latitude, fd.Longitude, float64(offset)/3600) sunrise, sunset, err := sunCalc.Calculate(firstTime) if err != nil { // Use defaults if calculation fails sunrise = firstTime.Add(8 * time.Hour) sunset = firstTime.Add(16 * time.Hour) } // 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 current.LocalTime = time.Unix(timestamp, 0).In(loc).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 if val, err := fd.getFloatValue(index, "WindSpeedMS"); err == nil && !math.IsNaN(val) { current.WindSpeed = val } // Extract wind direction if val, err := fd.getFloatValue(index, "WindDirection"); err == nil && !math.IsNaN(val) { current.WindDeg = int(math.Round(val)) } // Extract wind gust if val, err := fd.getFloatValue(index, "WindGust"); err == nil && !math.IsNaN(val) { current.WindGust = val } // 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 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)) forecastTime := time.Unix(timestamp, 0) current.Weather = []Weather{mapper.Map(symbol, forecastTime, sunrise, sunset)} } 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) }