Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building a Qt Weather Forecast Interface with HTTP Data Fetching and JSON Parsing

Tech May 10 3

Build a weather forecast interface that retrieves and displays meteorological data for various cities, encompassing Qt Stylesheet-based UI customization, HTTP network communication, JSON data parsing, custom temperature curve visualization, and end-to-end integration and debugging of multiple components.

UI Design and Qt Stylesheet Customization

Layout Structure

Organize the interface into functional upper and lower sections with consistent widget naming conventions.

Stylesheet Customization Techniques

Border Radius Settings

border-radius: 4px;

Direction-Specific Border Radius

border-bottom-left-radius: 0;
border-bottom-right-radius: 0;

Background and Font Styling

background-color: rgba(60, 60, 60, 100);
color: rgb(230, 230, 230);
font: 45pt "Segoe UI";

Multi-State Button Styling

QPushButton#btnRefresh {
    background-color: rgba(220, 250, 220, 255);
    border: 10px solid white;
    border-radius: 32px;
    font-size: 60px;
}
QPushButton#btnRefresh:hover {
    background-color: rgba(255, 255, 0, 255);
    border: 10px solid white;
    border-radius: 32px;
    color: #FF00FE;
    font-size: 60px;
}
QPushButton#btnRefresh:pressed {
    background-color: rgba(255, 180, 0, 255);
    border: 10px solid white;
    border-radius: 32px;
    color: #FF00FE;
    font-size: 60px;
}

Inheritance-Based Styling

Parent widgets can define styles for child widgets, which apply unless overridden:

QLabel {
    background-color: rgba(0, 200, 200, 200);
    border-radius: 4px;
}

Weather Data APIs

Use public JSON-based weather APIs to fetch data. Validate responses with online tools like JSON Formatter & Validator.

Available APIs

  1. City-specific weather endpoint: http://t.weather.itboy.net/api/weather/city/101010100
  2. Yiketianqi API:
    • Current location weather: http://v1.yiketianqi.com/api?unescape=1&version=v61&appid=89361488&appsecret=K6tTmCT0
    • Target city daily weather: http://v1.yiketianqi.com/api?unescape=1&version=v61&appid=89361488&appsecret=K6tTmCT0&cityid=<CITY_ID>
    • 7-day forecast for target city: http://v1.yiketianqi.com/api?unescape=1&version=v9&appid=89361488&appsecret=K6tTmCT0&cityid=<CITY_ID>

JSON Data Handling in Qt

Overview of JSON

JSON (JavaScript Object Notation) is a lightweight data interchange format using key-value pairs, ideal for network communication due to its readability, compact size, cross-language suport, and ease of parsing. It is widely used in web APIs and cross-system data exchange.

Generating JSON Data

Create and save structured JSON data using Qt's QJsonDocument, QJsonObject, and QJsonArray classes:

Sample JSON Output:

{
    "cityId": 1010200,
    "date": "2024-03-15",
    "dataEntries": [
        "forecast1",
        "forecast2",
        200
    ],
    "weatherCondition": "Snowy"
}

Qt Implementation:

void generateWeatherSampleJson() {
    QJsonObject mainJsonObject;
    mainJsonObject["cityId"] = 1010200;
    mainJsonObject["date"] = "2024-03-15";
    mainJsonObject["weatherCondition"] = "Snowy";

    QJsonArray dataEntries;
    dataEntries.append("forecast1");
    dataEntries.append("forecast2");
    dataEntries.append(200);
    mainJsonObject["dataEntries"] = dataEntries;

    QJsonObject alarmObj;
    alarmObj["alarmType"] = "Snow Warning";
    alarmObj["alarmLevel"] = "Orange";
    alarmObj["alarmContent"] = "Shanghai issues orange snow warning";
    mainJsonObject["weatherAlarm"] = alarmObj;

    QJsonArray dailyForecastArray;
    QJsonObject day1;
    day1["weekday"] = "Friday";
    day1["date"] = "2024-03-15";
    day1["weather"] = "Snowy";
    day1["temperature"] = 5;
    dailyForecastArray.append(day1);

    QJsonObject day2;
    day2["weekday"] = "Saturday";
    day2["date"] = "2024-03-16";
    day2["weather"] = "Cloudy";
    day2["temperature"] = 8;
    dailyForecastArray.append(day2);

    mainJsonObject["dailyForecast"] = dailyForecastArray;

    QJsonDocument jsonDoc(mainJsonObject);
    QByteArray jsonBytes = jsonDoc.toJson(QJsonDocument::Indented);

    QFile outputFile("C:/temp/weather_sample.json");
    if (!outputFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        qDebug() << "Failed to open file for writing";
        return;
    }
    outputFile.write(jsonBytes);
    outputFile.close();
}

Parsing JSON Data

Parse JSON content from a file or network response using Qt's JSON classes:

void loadAndParseJsonData() {
    QFile inputFile("C:/temp/weather_sample.json");
    if (!inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qDebug() << "Failed to open JSON file";
        return;
    }
    QString jsonContent = inputFile.readAll();
    inputFile.close();

    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonContent.toUtf8());
    if (jsonDoc.isNull() || !jsonDoc.isObject()) {
        qDebug() << "Invalid JSON data";
        return;
    }

    QJsonObject weatherDataObj = jsonDoc.object();
    qDebug() << "--- Root Object Data ---";
    int cityId = weatherDataObj["cityId"].toInt();
    QString date = weatherDataObj["date"].toString();
    QString weather = weatherDataObj["weatherCondition"].toString();
    qDebug() << "City ID:" << cityId;
    qDebug() << "Date:" << date;
    qDebug() << "Weather:" << weather;

    if (weatherDataObj.contains("dataEntries") && weatherDataObj["dataEntries"].isArray()) {
        QJsonArray dataArray = weatherDataObj["dataEntries"].toArray();
        qDebug() << "--- Array Data ---";
        int index = 0;
        for (const QJsonValue& val : dataArray) {
            switch (val.type()) {
                case QJsonValue::String:
                    qDebug() << "Entry" << index << ":" << val.toString();
                    break;
                case QJsonValue::Double:
                    qDebug() << "Entry" << index << ":" << val.toInt();
                    break;
                default:
                    qDebug() << "Entry" << index << ": Unsupported type";
                    break;
            }
            index++;
        }
    }

    if (weatherDataObj.contains("weatherAlarm") && weatherDataObj["weatherAlarm"].isObject()) {
        QJsonObject alarmData = weatherDataObj["weatherAlarm"].toObject();
        qDebug() << "--- Weather Alarm ---";
        qDebug() << "Type:" << alarmData["alarmType"].toString();
        qDebug() << "Level:" << alarmData["alarmLevel"].toString();
        qDebug() << "Content:" << alarmData["alarmContent"].toString();
    }
}

Parsing JSON to QMap

Convert simple JSON key-value pairs to a QMap for easy access:

void parseJsonToQMap() {
    QString jsonString = R"(
        {
            "userName": "Jane Smith",
            "age": "28",
            "contact": "jane.smith@example.com"
        }
    )";

    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8());
    QMap<QString, QString> userDataMap;

    if (!jsonDoc.isNull() && jsonDoc.isObject()) {
        QJsonObject userObj = jsonDoc.object();
        for (const QString& key : userObj.keys()) {
            userDataMap[key] = userObj[key].toString();
        }
    } else {
        qDebug() << "Invalid JSON input";
        return;
    }

    qDebug() << "--- User Data Map ---";
    for (const QString& key : userDataMap.keys()) {
        qDebug() << key << ":" << userDataMap[key];
    }
}

Network Architecture and HTTP Basics

Client-Server (CS) vs Browser-Server (BS) Architectures

  • CS Architecture: Two-tier system with dedicated client app and server. Clients handle UI and local processing, servers manage data and business logic. Requires platform-specific client development, offers better performance and rich UI but higher maintenance cost.
  • BS Architecture: Web-based system using standard browsers as clients. All business logic runs on servers, enabling cross-platform access with minimal client setup. Lower maintenance cost but limited by web technology capabilities and network performance.

Core HTTP Concepts

HTTP (Hypertext Transfer Protocol) is the foundation of web communication, using request-response model:

  • Request Methods: GET (retrieve resources), POST (submit data), PUT (update resources), DELETE (remove resources), HEAD (get metadata).
  • Status Codes: 200 OK (success), 404 Not Found (resource missing), 500 Internal Server Error (server failure), 301 Moved Permanently (resource relocated).
  • Headers: Metadata like Content-Type (response format), User-Agent (client info).
  • Statelessness: No built-in session tracking; cookies or tokens are used to maintain state between requests.
  • HTTPS: Secure HTTP with SSL/TLS encryption for data confidentiality.

Qt HTTP Programming

Use Qt's QNetworkAccessManager, QNetworkRequest, and QNetworkReply for asynchronous HTTP communication:

#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QDebug>

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    QNetworkAccessManager networkManager;
    QNetworkRequest request(QUrl("https://jsonplaceholder.typicode.com/todos/1"));
    QNetworkReply* httpReply = networkManager.get(request);

    QObject::connect(httpReply, &QNetworkReply::finished, [&]() {
        if (httpReply->error() != QNetworkReply::NoError) {
            qDebug() << "Request error:" << httpReply->errorString();
            httpReply->deleteLater();
            return;
        }

        QString responseData = httpReply->readAll();
        qDebug() << "Response received:" << responseData;
        httpReply->deleteLater();
    });

    return app.exec();
}

Weather Forecast Project Integration

Fetch, Parse, and Refresh UI

Use QNetworkAccessManager to fetch weather data, parse JSON resposne, and update UI components:

  1. Initialize Network Request:
// In widget constructor
QNetworkAccessManager* networkManager = new QNetworkAccessManager(this);
QString baseApiUrl = "http://v1.yiketianqi.com/api?unescape=1&version=v9&appid=89361488&appsecret=K6tTmCT0";
QNetworkRequest apiRequest(QUrl(baseApiUrl));
networkManager->get(apiRequest);

connect(networkManager, &QNetworkAccessManager::finished, this, &WeatherWidget::processWeatherApiResponse);
  1. Process API Response:
class WeatherDay {
public:
    QString cityName;
    QString date;
    QString weekday;
    QString currentTemp;
    QString weatherType;
    QString lowTemp;
    QString highTemp;
    QString weatherTips;
    QString windDirection;
    QString windSpeed;
    QString pm25;
    QString humidity;
    QString airQuality;
};

WeatherDay weeklyWeatherData[7];

void WeatherWidget::processWeatherApiResponse(QNetworkReply* reply) {
    int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    if (reply->error() != QNetworkReply::NoError || responseCode != 200) {
        qDebug() << "Network error:" << reply->errorString();
        QMessageBox::critical(this, "Error", "Failed to fetch weather data.");
        reply->deleteLater();
        return;
    }

    QByteArray responseBytes = reply->readAll();
    parseWeatherJsonResponse(responseBytes);
    reply->deleteLater();
}
  1. Parse JSON to WeatherDay Objects:
void WeatherWidget::parseWeatherJsonResponse(QByteArray jsonBytes) {
    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonBytes);
    if (jsonDoc.isNull() || !jsonDoc.isObject()) {
        qDebug() << "Invalid weather JSON data";
        return;
    }

    QJsonObject rootObj = jsonDoc.object();
    weeklyWeatherData[0].cityName = rootObj["city"].toString();
    weeklyWeatherData[0].pm25 = rootObj["aqi"].toObject()["pm25"].toString();

    if (rootObj.contains("data") && rootObj["data"].isArray()) {
        QJsonArray forecastArray = rootObj["data"].toArray();
        for (int i = 0; i < qMin(7, (int)forecastArray.size()); i++) {
            QJsonObject dayObj = forecastArray[i].toObject();
            weeklyWeatherData[i].weekday = dayObj["week"].toString();
            weeklyWeatherData[i].date = dayObj["date"].toString();
            weeklyWeatherData[i].weatherType = dayObj["wea"].toString();
            weeklyWeatherData[i].airQuality = dayObj["air_level"].toString();
            weeklyWeatherData[i].windDirection = dayObj["win"].toArray()[0].toString();
            weeklyWeatherData[i].windSpeed = dayObj["win_speed"].toString();
            weeklyWeatherData[i].currentTemp = dayObj["tem"].toString();
            weeklyWeatherData[i].lowTemp = dayObj["tem2"].toString();
            weeklyWeatherData[i].highTemp = dayObj["tem1"].toString();
            weeklyWeatherData[i].weatherTips = dayObj["index"].toArray()[3].toObject()["desc"].toString();
            weeklyWeatherData[i].humidity = dayObj["humidity"].toString();
        }
    }

    refreshWeatherDisplay();
}
  1. Update UI Components:
void WeatherWidget::refreshWeatherDisplay() {
    ui->lblCurrentDate->setText(weeklyWeatherData[0].date + " " + weeklyWeatherData[0].weekday);
    ui->lblCityName->setText(weeklyWeatherData[0].cityName);
    ui->lblCurrentTemp->setText(weeklyWeatherData[0].currentTemp + "℃");
    ui->lblTempRange->setText(weeklyWeatherData[0].lowTemp + "℃ - " + weeklyWeatherData[0].highTemp + "℃");
    ui->lblWeatherType->setText(weeklyWeatherData[0].weatherType);
    ui->lblWeatherIcon->setPixmap(getWeatherIcon(weeklyWeatherData[0].weatherType));

    ui->lblWeatherTips->setText("Tips: " + weeklyWeatherData[0].weatherTips);
    ui->lblWindDir->setText(weeklyWeatherData[0].windDirection);
    ui->lblWindSpeed->setText(weeklyWeatherData[0].windSpeed);
    ui->lblPM25->setText(weeklyWeatherData[0].pm25);
    ui->lblHumidity->setText(weeklyWeatherData[0].humidity);
    ui->lblAirQuality->setText(weeklyWeatherData[0].airQuality);

    QStringList weekdayLabels = {"Today", "Tomorrow", "Day After", "4th Day", "5th Day", "6th Day", "7th Day"};
    for (int i = 0; i < 7; i++) {
        ui->lblWeekday[i]->setText(weekdayLabels[i]);
        QStringList dateParts = weeklyWeatherData[i].date.split("-");
        ui->lblShortDate[i]->setText(dateParts[1] + "-" + dateParts[2]);
        ui->lblDayWeatherType[i]->setText(weeklyWeatherData[i].weatherType);
        ui->lblDayWeatherIcon[i]->setPixmap(getWeatherIcon(weeklyWeatherData[i].weatherType));

        QString airQuality = weeklyWeatherData[i].airQuality;
        QString styleSheet = "border-radius: 8px; color: rgb(230,230,230); ";
        if (airQuality == "Excellent") styleSheet += "background-color: rgb(124,198,55);";
        else if (airQuality == "Good") styleSheet += "background-color: rgb(208,107,39);";
        else if (airQuality == "Light Pollution") styleSheet += "background-color: rgb(255,200,200);";
        else if (airQuality == "Moderate Pollution") styleSheet += "background-color: rgb(255,17,17);";
        else if (airQuality == "Severe Pollution") styleSheet += "background-color: rgb(153,0,0);";
        ui->lblAirQualityBadge[i]->setStyleSheet(styleSheet);
        ui->lblAirQualityBadge[i]->setText(airQuality);

        ui->lblDayWindDir[i]->setText(weeklyWeatherData[i].windDirection);
        ui->lblDayWindSpeed[i]->setText(weeklyWeatherData[i].windSpeed.split(" turn ").first());
    }

    update();
}

City Search Functionality

Implement city lookup by name using a preloaded city code JSON file:

  1. Load City Codes into QMap:
class CityCodeLookup {
private:
    QMap<QString, QString> cityCodeMap;

    void loadCityCodeData() {
        QFile cityFile("C:/temp/city_codes.json");
        if (!cityFile.open(QIODevice::ReadOnly)) {
            qDebug() << "Failed to load city codes file";
            return;
        }
        QString cityJson = cityFile.readAll();
        cityFile.close();

        QJsonDocument cityDoc = QJsonDocument::fromJson(cityJson.toUtf8());
        if (cityDoc.isArray()) {
            QJsonArray cityArray = cityDoc.array();
            for (const QJsonValue& cityVal : cityArray) {
                if (cityVal.isObject()) {
                    QJsonObject cityObj = cityVal.toObject();
                    QString cityName = cityObj["city_name"].toString();
                    QString cityId = cityObj["city_code"].toString();
                    cityCodeMap[cityName] = cityId;
                }
            }
        }
    }

public:
    QString findCityIdByName(QString cityName) {
        if (cityCodeMap.isEmpty()) loadCityCodeData();

        if (cityCodeMap.contains(cityName)) return cityCodeMap[cityName];
        if (cityCodeMap.contains(cityName + " City")) return cityCodeMap[cityName + " City"];
        if (cityCodeMap.contains(cityName + " District")) return cityCodeMap[cityName + " District"];
        if (cityCodeMap.contains(cityName + " County")) return cityCodeMap[cityName + " County"];
        return "";
    }
};
  1. Search Button Click Handler:
void WeatherWidget::on_btnSearch_clicked() {
    QString cityName = ui->txtCitySearch->text().trimmed();
    if (cityName.isEmpty()) return;

    CityCodeLookup cityLookup;
    QString cityId = cityLookup.findCityIdByName(cityName);
    if (cityId.isEmpty()) {
        QMessageBox::warning(this, "Warning", "Invalid city name. Please enter a valid city.");
        return;
    }

    QString apiUrl = "http://v1.yiketianqi.com/api?unescape=1&version=v9&appid=89361488&appsecret=K6tTmCT0&cityid=" + cityId;
    networkManager->get(QNetworkRequest(QUrl(apiUrl)));
}

Custom Temperature Curve Drawing

Implement custom temperature curve visualization using Qt's event filter and QPainter:

  1. Instal Event Filter:
WeatherWidget::WeatherWidget(QWidget *parent) : QWidget(parent), ui(new Ui::WeatherWidget) {
    ui->setupUi(this);
    ui->highTempCanvas->installEventFilter(this);
    ui->lowTempCanvas->installEventFilter(this);
}
  1. Event Filter Implementation:
bool WeatherWidget::eventFilter(QObject* watched, QEvent* event) {
    if (watched == ui->highTempCanvas && event->type() == QEvent::Paint) {
        drawHighTemperatureCurve();
        return true;
    }
    if (watched == ui->lowTempCanvas && event->type() == QEvent::Paint) {
        drawLowTemperatureCurve();
        return true;
    }
    return QWidget::eventFilter(watched, event);
}
  1. Draw High Temperature Curve:
void WeatherWidget::drawHighTemperatureCurve() {
    QPainter painter(ui->highTempCanvas);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setPen(QColor(255, 100, 50));
    painter.setBrush(QColor(255, 100, 50));

    int totalHigh = 0;
    for (int i = 0; i < 7; i++) {
        totalHigh += weeklyWeatherData[i].highTemp.toInt();
    }
    float avgHigh = totalHigh / 7.0f;

    QPoint tempPoints[7];
    for (int i = 0; i < 7; i++) {
        int xPos = ui->lblAirQualityBadge[i]->x() + ui->lblAirQualityBadge[i]->width() / 2;
        int tempVal = weeklyWeatherData[i].highTemp.toInt();
        int yPos = ui->highTempCanvas->height() / 2 - (tempVal - avgHigh) * 4;
        tempPoints[i] = QPoint(xPos, yPos);

        painter.drawText(QPoint(xPos - 20, yPos - 15), weeklyWeatherData[i].highTemp + "℃");
        painter.drawEllipse(tempPoints[i], 4, 4);
    }

    for (int i = 0; i < 6; i++) {
        painter.drawLine(tempPoints[i], tempPoints[i+1]);
    }
}
  1. Draw Low Temperature Curve:
void WeatherWidget::drawLowTemperatureCurve() {
    QPainter painter(ui->lowTempCanvas);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setPen(QColor(50, 150, 255));
    painter.setBrush(QColor(50, 150, 255));

    int totalLow = 0;
    for (int i = 0; i < 7; i++) {
        totalLow += weeklyWeatherData[i].lowTemp.toInt();
    }
    float avgLow = totalLow / 7.0f;

    QPoint tempPoints[7];
    for (int i = 0; i < 7; i++) {
        int xPos = ui->lblAirQualityBadge[i]->x() + ui->lblAirQualityBadge[i]->width() / 2;
        int tempVal = weeklyWeatherData[i].lowTemp.toInt();
        int yPos = ui->lowTempCanvas->height() / 2 - (tempVal - avgLow) * 4;
        tempPoints[i] = QPoint(xPos, yPos);

        painter.drawText(QPoint(xPos - 20, yPos - 15), weeklyWeatherData[i].lowTemp + "℃");
        painter.drawEllipse(tempPoints[i], 4, 4);
    }

    for (int i = 0; i < 6; i++) {
        painter.drawLine(tempPoints[i], tempPoints[i+1]);
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.