// fmi_client.go - FMI API client library package main import ( "fmt" "io" "net/http" "net/url" "time" ) // FMIClient handles FMI API interactions type FMIClient struct { BaseURL string HTTPClient *http.Client WeatherMapper *WeatherMapper } // NewFMIClient creates a new FMI client func NewFMIClient() *FMIClient { return &FMIClient{ BaseURL: "https://opendata.fmi.fi/wfs", HTTPClient: &http.Client{Timeout: 30 * time.Second}, WeatherMapper: NewWeatherMapper(), } } // GetForecast fetches and parses forecast for a location func (c *FMIClient) GetForecast(location string) (*OWMResponse, error) { // Build request URL url, err := c.buildURL(location) if err != nil { return nil, fmt.Errorf("building URL: %w", err) } // Fetch XML data xmlData, err := c.fetchXML(url) if err != nil { return nil, fmt.Errorf("fetching XML: %w", err) } // Parse XML forecastData, err := ParseForecastXML(xmlData) if err != nil { return nil, fmt.Errorf("parsing XML: %w", err) } // Convert to OWM format response, err := forecastData.ToOWMResponse(c.WeatherMapper) if err != nil { return nil, fmt.Errorf("converting to OWM format: %w", err) } return response, nil } func (c *FMIClient) buildURL(location string) (string, error) { baseURL, err := url.Parse(c.BaseURL) if err != nil { return "", err } q := baseURL.Query() q.Set("service", "WFS") q.Set("version", "2.0.0") q.Set("request", "getFeature") q.Set("storedquery_id", "fmi::forecast::harmonie::surface::point::multipointcoverage") q.Set("place", location) q.Set("parameters", "Temperature,PrecipitationRate,Humidity,Pressure,dewPoint,visibility,WindSpeedMS,WindDirection,WindGust,TotalCloudCover,WeatherSymbol3") baseURL.RawQuery = q.Encode() return baseURL.String(), nil } func (c *FMIClient) fetchXML(url string) ([]byte, error) { resp, err := c.HTTPClient.Get(url) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) } return io.ReadAll(resp.Body) }