Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Data Fetching: From City Name to Weather Forecast

The Weather Application retrieves real-time weather information from the Open-Meteo API. However, weather services typically do not accept city names directly when requesting weather data. Instead, they require geographic coordinates, specifically latitude and longitude.

For this reason, the Weather App performs the data retrieval process in two stages:

  1. Convert the city name into geographic coordinates using the Open-Meteo Geocoding API.
  2. Use those coordinates to request weather information from the Open-Meteo Forecast API.

This two-step design reflects how many modern weather systems operate and allows the application to separate responsibilities clearly.

1 Geocoding: Converting City Names to Coordinates

When a user enters a city such as:

Berlin

the weather API cannot directly use the text value. Instead, it requires precise coordinates, for example:

latitude  = 52.52
longitude = 13.41

Therefore, the application first sends the city name to the geocoding service, which translates the human-readable location into machine-readable coordinates.

Constructing the Geocoding Request

The geocoding request URL is constructed dynamically based on the user's input.

A simplified version of the request construction is shown below.

#![allow(unused)]
fn main() {
let url = format!(
    "https://geocoding-api.open-meteo.com/v1/search?name={}&count={}",
    urlencoding::encode(q),
    count
);
}

The parameters included in this request are:

  • name – the city entered by the user
  • count – the maximum number of search results
  • urlencoding::encode() – ensures spaces and special characters are safely encoded for URLs

This encoding step is important because city names such as New York contain spaces that must be safely transmitted over HTTP.

Geocoding Response Models

The JSON response returned by the geocoding API is mapped into Rust structures using Serde.

#![allow(unused)]
fn main() {
#[derive(Debug, Deserialize)]
struct GeoResponse {
    results: Option<Vec<GeoCity>>,
}

#[derive(Debug, Deserialize, Clone)]
struct GeoCity {
    name: String,
    latitude: f64,
    longitude: f64,
}
}

These models mirror the structure of the API response.

Each GeoCity object contains:

  • the city name
  • its latitude
  • its longitude

Mapping JSON data into typed Rust structures ensures that the application can work with the data safely and predictably.

Selecting the Matching City

After the geocoding request completes, the application selects the first matching city from the results.

#![allow(unused)]
fn main() {
let city0 = geo.get(0)
    .cloned()
    .ok_or_else(|| "City not found".to_string())?;
}

If a result exists, its coordinates are used. If no result is returned, the function generates an error instead of continuing with invalid data.

This prevents runtime crashes and ensures robust behavior.

2 Fetching Weather Data

Once the geographic coordinates are available, the application can request weather data from the forecast API.

The coordinates are inserted into the request URL.

#![allow(unused)]
fn main() {
let weather_url = format!(
"https://api.open-meteo.com/v1/forecast?latitude={}&longitude={}&current=temperature_2m,weather_code&daily=weather_code,temperature_2m_max,temperature_2m_min&forecast_days=7&timezone=auto",
city0.latitude,
city0.longitude
);
}

This request retrieves:

  • the current temperature
  • the current weather condition code
  • the maximum temperature for each day
  • the minimum temperature for each day
  • the weather condition codes for the forecast
  • a 7-day forecast

The timezone=auto parameter ensures the forecast is returned in the correct local time.

Weather Response Models

The JSON returned by the weather API is also converted into Rust data structures.

#![allow(unused)]
fn main() {
#[derive(Debug, Deserialize)]
struct WeatherResponse {
    current: Option<CurrentWeather>,
    daily: Option<DailyWeather>,
}
}

The current weather model is defined as:

#![allow(unused)]
fn main() {
#[derive(Debug, Deserialize)]
struct CurrentWeather {
    temperature_2m: f64,
    weather_code: i32,
}
}

The daily forecast model is defined as:

#![allow(unused)]
fn main() {
#[derive(Debug, Deserialize)]
struct DailyWeather {
    time: Vec<String>,
    temperature_2m_max: Vec<f64>,
    temperature_2m_min: Vec<f64>,
    weather_code: Vec<i32>,
}
}

These structures allow the program to access weather values directly using Rust field names instead of manually parsing JSON.

Sending the Weather Request

The weather request is sent using the reqwest library.

#![allow(unused)]
fn main() {
let w: WeatherResponse = reqwest::blocking::get(&weather_url)?
    .json()?;
}

This code performs several actions automatically:

  1. Sends an HTTP request to the Open-Meteo API
  2. Receives a JSON response
  3. Converts the JSON into the WeatherResponse structure

This automatic conversion is handled by Serde.

Extracting Weather Information

After the JSON response is converted into Rust data, the application extracts the current weather and forecast values.

For example:

#![allow(unused)]
fn main() {
let current = w.current.ok_or_else(|| "No current weather".to_string())?;
}

This safely retrieves the current weather data while handling the possibility that the API response might be incomplete.

Similarly, the daily forecast is accessed through:

#![allow(unused)]
fn main() {
let daily = w.daily.ok_or_else(|| "No daily forecast".to_string())?;
}

The application then loops through the forecast arrays to build the data used by the user interface.

#![allow(unused)]
fn main() {
for i in 0..n {
    rows.push((
        daily.time[i].clone(),
        daily.temperature_2m_max[i],
        daily.temperature_2m_min[i],
        daily.weather_code[i],
    ));
}
}

Each entry represents one day in the forecast.

Integration with the User Interface

The data returned from the fetch function is passed to the UI layer.

The function returns structured weather information in the following format:

#![allow(unused)]
fn main() {
Ok((city0.name, current.temperature_2m, current.weather_code, rows))
}

The graphical interface then uses this data to update:

  • the current weather display
  • the weather icon
  • the seven-day forecast list

The UI itself does not interact directly with the API. Instead, it only receives processed data from the fetch logic.

This separation improves readability, maintainability, and overall application design.