diff --git a/WeatherRoutes/CMakeLists.txt b/WeatherRoutes/CMakeLists.txt new file mode 100644 index 0000000..1f230d5 --- /dev/null +++ b/WeatherRoutes/CMakeLists.txt @@ -0,0 +1,81 @@ +cmake_minimum_required(VERSION 3.16) + +project(WeatherRoutes VERSION 0.1 LANGUAGES CXX) + +# Define versioning information +set(VERSION_CODE 1) # Change this to your desired version code +set(VERSION_NAME "0.1") # Your version name + +# Include this in the Android section +set(ANDROID_PACKAGE_NAME "com.example.weatherroutes") +set(ANDROID_VERSION_CODE ${VERSION_CODE}) +set(ANDROID_VERSION_NAME ${VERSION_NAME}) + +set(CMAKE_ANDROID_MANIFEST "${CMAKE_CURRENT_SOURCE_DIR}/android/AndroidManifest.xml") + + +set(CMAKE_AUTOMOC ON) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(RESOURCE_FILES resources.qrc) + +find_package(Qt6 REQUIRED COMPONENTS Quick) +find_package(Qt6 REQUIRED COMPONENTS Core) +find_package(Qt6 REQUIRED COMPONENTS Location) +find_package(Qt6 REQUIRED COMPONENTS Positioning) +find_package(Qt6 REQUIRED COMPONENTS Network) + +qt_add_resources(RESOURCES_RCC "${RESOURCE_FILES}") + +qt_add_executable(appWeatherRoutes + main.cpp + ${RESOURCES_RCC} +) + +qt_add_qml_module(appWeatherRoutes + URI WeatherRoutes + VERSION 1.0 + QML_FILES + Main.qml + SOURCES + QML_FILES + QML_FILES qml/WaypointData.qml + QML_FILES qml/WeatherDetailsView.qml + QML_FILES qml/CustomInputField.qml + QML_FILES qml/MapView.qml + QML_FILES + QML_FILES qml/HomeScreen.qml + SOURCES mvc/data/waypoint.h mvc/data/waypoint.cpp + SOURCES mvc/data/mapdata.cpp mvc/data/mapdata.h + SOURCES mvc/model/mapdatamodel.h mvc/model/mapdatamodel.cpp + SOURCES mvc/data/weatherdetailsdata.h mvc/data/weatherdetailsdata.cpp + SOURCES mvc/enums/weatherstatus.h + SOURCES mvc/data/weatherdata.h mvc/data/weatherdata.cpp + SOURCES mvc/controller/weathercontroller.h mvc/controller/weathercontroller.cpp + SOURCES mvc/controller/networkmanager.h mvc/controller/networkmanager.cpp + SOURCES mvc/controller/stylecontroller.h mvc/controller/stylecontroller.cpp +) + +target_include_directories(appWeatherRoutes + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/mvc +) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +set_target_properties(appWeatherRoutes PROPERTIES +# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appWeatherRoutes + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) +target_link_libraries(appWeatherRoutes PRIVATE Qt6::Quick Qt6::Location Qt6::Positioning) +set(CMAKE_AUTORCC ON) + +include(GNUInstallDirs) +install(TARGETS appWeatherRoutes + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/WeatherRoutes/Main.qml b/WeatherRoutes/Main.qml new file mode 100644 index 0000000..49e5f05 --- /dev/null +++ b/WeatherRoutes/Main.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Controls +import QtLocation +import QtPositioning + +ApplicationWindow { + visible: true + + width: Qt.platform.os == "android" ? Screen.width : 360 + height: Qt.platform.os == "android" ? Screen.height : 640 + title: "Weather Routes" + HomeScreen + { + id: homeScreen + anchors.fill: parent + } +} diff --git a/WeatherRoutes/android/AndroidManifest.xml b/WeatherRoutes/android/AndroidManifest.xml new file mode 100644 index 0000000..442e26c --- /dev/null +++ b/WeatherRoutes/android/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/WeatherRoutes/generate.py b/WeatherRoutes/generate.py new file mode 100644 index 0000000..918c488 --- /dev/null +++ b/WeatherRoutes/generate.py @@ -0,0 +1,14 @@ +import os + +resource_file = "resources.qrc" +image_folder = "images" + +with open(resource_file, "w") as f: + f.write('\n\n') + for root, _, files in os.walk(image_folder): + for file in files: + if file.endswith('.png'): + f.write(f' {os.path.join(image_folder, file)}\n') + f.write('\n\n') + +print(f"{resource_file} has been generated.") \ No newline at end of file diff --git a/WeatherRoutes/images/Destination_location.png b/WeatherRoutes/images/Destination_location.png new file mode 100644 index 0000000..93c7c89 Binary files /dev/null and b/WeatherRoutes/images/Destination_location.png differ diff --git a/WeatherRoutes/images/centerToGPS.png b/WeatherRoutes/images/centerToGPS.png new file mode 100644 index 0000000..25229ae Binary files /dev/null and b/WeatherRoutes/images/centerToGPS.png differ diff --git a/WeatherRoutes/images/currentLocationOfWaypoint.svg b/WeatherRoutes/images/currentLocationOfWaypoint.svg new file mode 100644 index 0000000..4780f25 --- /dev/null +++ b/WeatherRoutes/images/currentLocationOfWaypoint.svg @@ -0,0 +1,11 @@ + + + + Layer 1 + + + + + + + \ No newline at end of file diff --git a/WeatherRoutes/images/live_icons/bewolkt.png b/WeatherRoutes/images/live_icons/bewolkt.png new file mode 100644 index 0000000..5cc6c19 Binary files /dev/null and b/WeatherRoutes/images/live_icons/bewolkt.png differ diff --git a/WeatherRoutes/images/live_icons/bliksem.png b/WeatherRoutes/images/live_icons/bliksem.png new file mode 100644 index 0000000..1caf972 Binary files /dev/null and b/WeatherRoutes/images/live_icons/bliksem.png differ diff --git a/WeatherRoutes/images/live_icons/buien.png b/WeatherRoutes/images/live_icons/buien.png new file mode 100644 index 0000000..b71398c Binary files /dev/null and b/WeatherRoutes/images/live_icons/buien.png differ diff --git a/WeatherRoutes/images/live_icons/hagel.png b/WeatherRoutes/images/live_icons/hagel.png new file mode 100644 index 0000000..87b9489 Binary files /dev/null and b/WeatherRoutes/images/live_icons/hagel.png differ diff --git a/WeatherRoutes/images/live_icons/halfbewolkt.png b/WeatherRoutes/images/live_icons/halfbewolkt.png new file mode 100644 index 0000000..5d9e648 Binary files /dev/null and b/WeatherRoutes/images/live_icons/halfbewolkt.png differ diff --git a/WeatherRoutes/images/live_icons/helderenacht.png b/WeatherRoutes/images/live_icons/helderenacht.png new file mode 100644 index 0000000..fd2a43d Binary files /dev/null and b/WeatherRoutes/images/live_icons/helderenacht.png differ diff --git a/WeatherRoutes/images/live_icons/lichtbewolkt.png b/WeatherRoutes/images/live_icons/lichtbewolkt.png new file mode 100644 index 0000000..5d9e648 Binary files /dev/null and b/WeatherRoutes/images/live_icons/lichtbewolkt.png differ diff --git a/WeatherRoutes/images/live_icons/mist.png b/WeatherRoutes/images/live_icons/mist.png new file mode 100644 index 0000000..0955237 Binary files /dev/null and b/WeatherRoutes/images/live_icons/mist.png differ diff --git a/WeatherRoutes/images/live_icons/nachtmist.png b/WeatherRoutes/images/live_icons/nachtmist.png new file mode 100644 index 0000000..87a8a41 Binary files /dev/null and b/WeatherRoutes/images/live_icons/nachtmist.png differ diff --git a/WeatherRoutes/images/live_icons/regen.png b/WeatherRoutes/images/live_icons/regen.png new file mode 100644 index 0000000..dca270c Binary files /dev/null and b/WeatherRoutes/images/live_icons/regen.png differ diff --git a/WeatherRoutes/images/live_icons/sneeuw.png b/WeatherRoutes/images/live_icons/sneeuw.png new file mode 100644 index 0000000..f383019 Binary files /dev/null and b/WeatherRoutes/images/live_icons/sneeuw.png differ diff --git a/WeatherRoutes/images/live_icons/wolkennacht.png b/WeatherRoutes/images/live_icons/wolkennacht.png new file mode 100644 index 0000000..efb5795 Binary files /dev/null and b/WeatherRoutes/images/live_icons/wolkennacht.png differ diff --git a/WeatherRoutes/images/live_icons/zonnig.png b/WeatherRoutes/images/live_icons/zonnig.png new file mode 100644 index 0000000..d995802 Binary files /dev/null and b/WeatherRoutes/images/live_icons/zonnig.png differ diff --git a/WeatherRoutes/images/live_icons/zwaarbewolkt.png b/WeatherRoutes/images/live_icons/zwaarbewolkt.png new file mode 100644 index 0000000..7258500 Binary files /dev/null and b/WeatherRoutes/images/live_icons/zwaarbewolkt.png differ diff --git a/WeatherRoutes/images/start_location.png b/WeatherRoutes/images/start_location.png new file mode 100644 index 0000000..e0f5b48 Binary files /dev/null and b/WeatherRoutes/images/start_location.png differ diff --git a/WeatherRoutes/images/start_location.svg b/WeatherRoutes/images/start_location.svg new file mode 100644 index 0000000..6b79840 --- /dev/null +++ b/WeatherRoutes/images/start_location.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/WeatherRoutes/images/weather_partly_cloudy_day.png b/WeatherRoutes/images/weather_partly_cloudy_day.png new file mode 100644 index 0000000..c481203 Binary files /dev/null and b/WeatherRoutes/images/weather_partly_cloudy_day.png differ diff --git a/WeatherRoutes/images/weather_rainy.png b/WeatherRoutes/images/weather_rainy.png new file mode 100644 index 0000000..021d815 Binary files /dev/null and b/WeatherRoutes/images/weather_rainy.png differ diff --git a/WeatherRoutes/images/weather_sunny.png b/WeatherRoutes/images/weather_sunny.png new file mode 100644 index 0000000..501dce2 Binary files /dev/null and b/WeatherRoutes/images/weather_sunny.png differ diff --git a/WeatherRoutes/images/weather_thunderstorm.png b/WeatherRoutes/images/weather_thunderstorm.png new file mode 100644 index 0000000..77628e9 Binary files /dev/null and b/WeatherRoutes/images/weather_thunderstorm.png differ diff --git a/WeatherRoutes/images/weather_tsunami.png b/WeatherRoutes/images/weather_tsunami.png new file mode 100644 index 0000000..ea67f25 Binary files /dev/null and b/WeatherRoutes/images/weather_tsunami.png differ diff --git a/WeatherRoutes/main.cpp b/WeatherRoutes/main.cpp new file mode 100644 index 0000000..bcd7367 --- /dev/null +++ b/WeatherRoutes/main.cpp @@ -0,0 +1,65 @@ +#include "controller/networkmanager.h" +#include "controller/weathercontroller.h" +#include "data/mapdata.h" +#include "data/weatherdetailsdata.h" +#include "controller/weathercontroller.h" +#include "controller/networkmanager.h" +#include "enums/weatherstatus.h" +#include +#include +#include +#include + +int main(int argc, char *argv[]) { +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard")); +#endif + + QGuiApplication app(argc, argv); + + QLocationPermission permission; + permission.setAccuracy(QLocationPermission::Precise); + permission.setAvailability(QLocationPermission::WhenInUse); + switch (qApp->checkPermission(permission)) { + case Qt::PermissionStatus::Undetermined: + qApp->requestPermission(permission, [](const QPermission &permission) { + if (permission.status() == Qt::PermissionStatus::Granted) { + qDebug() << "Permission granted !!"; + } + }); + case Qt::PermissionStatus::Granted: + case Qt::PermissionStatus::Denied: + break; + } + + QQmlApplicationEngine engine; + + MapData mapData; + mapData.setDefaultZoomLevel(15); + mapData.setZoomLevel(mapData.defaultZoomLevel()); + mapData.setGpsUpdateInterval(1000); + engine.rootContext()->setContextProperty("mapData", &mapData); + + WeatherController ctrl; + std::shared_ptr weatherCntrl = + std::make_shared(); + ctrl.parseJSONData(); + + WeatherDetailsData weatherDetailsData(weatherCntrl); + engine.rootContext()->setContextProperty("weatherDetailsData", + &weatherDetailsData); + engine.rootContext()->setContextProperty("networkManager", + NetworkManager::Instance()); + + const QUrl url(QStringLiteral("qrc:/WeatherRoutes/Main.qml")); + QObject::connect( + &engine, &QQmlApplicationEngine::objectCreated, &app, + [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, + Qt::QueuedConnection); + engine.load(url); + + return app.exec(); +} diff --git a/WeatherRoutes/mvc/controller/networkmanager.cpp b/WeatherRoutes/mvc/controller/networkmanager.cpp new file mode 100644 index 0000000..16afa88 --- /dev/null +++ b/WeatherRoutes/mvc/controller/networkmanager.cpp @@ -0,0 +1,42 @@ +#include "networkmanager.h" +#include "QStandardPaths" +#include +#include +#include +#include +#include + +NetworkManager *NetworkManager::m_instance; + +NetworkManager::NetworkManager(QObject *parent) : QObject{parent} {} + +NetworkManager *NetworkManager::Instance() { + if (m_instance != nullptr) { + return m_instance; + } + m_instance = new NetworkManager(); + return m_instance; +} + +void NetworkManager::fetchJSONfromAPI(QUrl url, QJsonDocument &doc) +{ + qDebug() << "Fetch URL::" << url.toDisplayString(); + QNetworkAccessManager m_manager; + QNetworkRequest request(url); + QNetworkReply *reply = m_manager.get(request); + QEventLoop loop; + connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); + if (reply->error() == QNetworkReply::NoError) + { + QByteArray response = reply->readAll(); + doc = QJsonDocument::fromJson(response); + } + else + { + qDebug() << "Invalid JSON response:: " << reply->errorString(); + } + + reply->deleteLater(); +} + diff --git a/WeatherRoutes/mvc/controller/networkmanager.h b/WeatherRoutes/mvc/controller/networkmanager.h new file mode 100644 index 0000000..7c7c1f3 --- /dev/null +++ b/WeatherRoutes/mvc/controller/networkmanager.h @@ -0,0 +1,22 @@ +#ifndef NETWORKMANAGER_H +#define NETWORKMANAGER_H + +#include + +class NetworkManager : public QObject { + Q_OBJECT +public: + void fetchJSONfromAPI(QUrl url, QJsonDocument &doc); + static NetworkManager *m_instance; + static NetworkManager *Instance(); + + NetworkManager(const NetworkManager&) = delete; + NetworkManager& operator=(const NetworkManager&) = delete; + +signals: + +private: + explicit NetworkManager(QObject *parent = nullptr); +}; + +#endif // NETWORKMANAGER_H diff --git a/WeatherRoutes/mvc/controller/stylecontroller.cpp b/WeatherRoutes/mvc/controller/stylecontroller.cpp new file mode 100644 index 0000000..15be671 --- /dev/null +++ b/WeatherRoutes/mvc/controller/stylecontroller.cpp @@ -0,0 +1,30 @@ +#include "stylecontroller.h" + +StyleController* StyleController::m_instance; + +StyleController *StyleController::Instance() +{ + if(m_instance != nullptr) + { + return m_instance; + } + m_instance = new StyleController(); + return m_instance; +} + +StyleController::StyleController(QObject *parent) + : QObject{parent} +{} + +int StyleController::colorMode() const +{ + return m_colorMode; +} + +void StyleController::setColorMode(int newColorMode) +{ + if (m_colorMode == newColorMode) + return; + m_colorMode = newColorMode; + emit colorModeChanged(); +} diff --git a/WeatherRoutes/mvc/controller/stylecontroller.h b/WeatherRoutes/mvc/controller/stylecontroller.h new file mode 100644 index 0000000..29c6d44 --- /dev/null +++ b/WeatherRoutes/mvc/controller/stylecontroller.h @@ -0,0 +1,59 @@ +#ifndef STYLECONTROLLER_H +#define STYLECONTROLLER_H + +#include +#include + +class StyleController : public QObject +{ + Q_OBJECT +public: + static StyleController* m_instance; + StyleController(const StyleController&) = delete; + StyleController& operator=(const StyleController&) = delete; + StyleController* Instance(); + + enum COLOR_MODE + { + DARK = 0, + LIGHT, + SYSTEM + } + Q_ENUMS(COLOR_MODE); + + + Q_PROPERTY(int colorMode READ colorMode WRITE setColorMode NOTIFY colorModeChanged FINAL) + + //Dark Mode + Q_PROPERTY(QColor textPrimary READ textPrimary NOTIFY colorChanged FINAL) + QColor textPrimary() const {return QColor(255,255,255);} + + Q_PROPERTY(QColor textSecondary READ textSecondary NOTIFY colorChanged FINAL) + QColor textSecondary() const {return QColor(255,255,255,179);} + + Q_PROPERTY(QColor textDisabled READ textDisabled NOTIFY colorChanged FINAL) + QColor textDisabled() const {return QColor(255,255,255,125);} + + Q_PROPERTY(QColor buttonActive READ buttonActive NOTIFY colorChanged FINAL) + QColor buttonActive() const {return QColor(255,255,255);} + + Q_PROPERTY(QColor buttonDisabled READ buttonDisabled NOTIFY colorChanged FINAL) + QColor buttonDisabled() const {return QColor(255,255,255, 77);} + + Q_PROPERTY(QColor buttonSelected READ buttonSelected NOTIFY colorChanged FINAL) + QColor buttonSelected() const {return QColor(255,255,255, 40);} + + int colorMode() const; + void setColorMode(int newColorMode); + + +signals: + void colorChanged(); + void colorModeChanged(); + +private: + explicit StyleController(QObject *parent = nullptr); + int m_colorMode = COLOR_MODE::DARK; +}; + +#endif // STYLECONTROLLER_H diff --git a/WeatherRoutes/mvc/controller/weathercontroller.cpp b/WeatherRoutes/mvc/controller/weathercontroller.cpp new file mode 100644 index 0000000..1446db7 --- /dev/null +++ b/WeatherRoutes/mvc/controller/weathercontroller.cpp @@ -0,0 +1,224 @@ +#include "weathercontroller.h" +#include "QDir" +#include "QFile" +#include "QGuiApplication" +#include "QStandardPaths" +#include "networkmanager.h" +#include +#include +#include +#include + +const QString WeatherController::FILENAME = "static.json"; + +WeatherController::WeatherController(QObject *parent) : QObject{parent} {} + +bool WeatherController::parseJSONData() { + QJsonDocument doc; + if (this->checkToDownload(doc)) { + QJsonObject jsonObject = doc.object(); + QJsonArray dataArray = jsonObject["data"].toArray(); + for (QJsonArray::const_iterator iter = dataArray.constBegin(); + iter != dataArray.constEnd(); ++iter) { + QJsonObject weatherObject = (*iter).toObject(); + + // Extract values from the JSON object + long tijd = weatherObject["tijd"].toString().toLong(); + QString tijd_nl = weatherObject["tijd_nl"].toString(); + int offset = weatherObject["offset"].toInt(); + float temp = weatherObject["temp"].toString().toFloat(); + int wind_ms = weatherObject["windb"].toInt(); + int wind_bf = weatherObject["winds"].toInt(); + int wind_knp = weatherObject["windknp"].toInt(); + int wind_kmh = weatherObject["windkmh"].toString().toFloat(); + int wind_r = weatherObject["windr"].toInt(); + QString wind_ltr = weatherObject["windrltr"].toString(); + int visibility = weatherObject["vis"].toInt(); + int neersl = weatherObject["neersl"].toString().toFloat() * + 10; // Converted from 0.1 to 1 decimal scale + float luchtd_bar = weatherObject["luchtd"].toString().toFloat(); + float luchtdmmhg = weatherObject["luchtdmmhg"].toString().toFloat(); + float luchtdinHg = weatherObject["luchtdinhg"].toString().toFloat(); + int hw = weatherObject["hw"].toInt(); + int mw = weatherObject["mw"].toInt(); + int lw = weatherObject["lw"].toInt(); + int tw = weatherObject["tw"].toInt(); + int rv = weatherObject["rv"].toInt(); + int gr = weatherObject["gr"].toInt(); + int gr_w = weatherObject["gr_w"].toInt(); + QString cape = weatherObject["cape"].toString(); + int snd = weatherObject["snd"].toInt(); + int snv = weatherObject["snv"].toInt(); + int cond = weatherObject["cond"].toInt(); + int iconCode = weatherObject["ico"].toInt(); + QString sameenv = weatherObject["samenv"].toString(); + QString icoon = weatherObject["icoon"].toString(); + + // Create WeatherData object and add it to the list using initializer list + // constructor + mData.append(WeatherData(tijd, tijd_nl, offset, temp, wind_ms, wind_bf, + wind_knp, wind_kmh, wind_r, wind_ltr, visibility, + neersl, luchtd_bar, luchtdmmhg, luchtdinHg, hw, + mw, lw, tw, rv, gr, gr_w, cape, snd, snv, cond, + iconCode, sameenv, icoon)); + } + } + else + return false; + + return true; +} + +QJsonObject WeatherController::fetchCurrentWeatherData(const QGeoCoordinate &coord) +{ + QJsonDocument doc; + readWeatherData(doc, "liveweer"); + QJsonArray liveweerArray = doc.array(); + QJsonObject firstEntry = liveweerArray.at(0).toObject(); + + //Will fetch live data when the time difference is more than 10 mins from last fetch + if (qAbs(QDateTime::currentSecsSinceEpoch() - firstEntry["timestamp"].toString().toUInt()) > 600) + { + // NetworkManager::Instance()->fetchJSONfromAPI(prepareURL(coord), doc); + // if(!doc.isNull() && doc.isObject()) + // { + // updateWeatherData(doc); + // } + } + return doc.object(); +} + +bool WeatherController::readWeatherData(QJsonDocument &doc, QString objName) +{ + QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); + QString fileName = cacheDir + "/" + WeatherController::FILENAME; + + QFile file(fileName); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream in(&file); + QJsonParseError error; + doc = QJsonDocument::fromJson(in.readAll().toStdString().c_str(), &error); + file.close(); + if(doc.isNull()) + { + qWarning() << "Error:: " << error.errorString(); + return false; + } + + if (!doc.isObject()) { + qWarning() << "Invalid JSON format"; + return false; + } + if(!objName.isNull()) + { + QJsonArray ary = doc.object()[objName].toArray(); + QJsonDocument temp_doc(ary); + doc = temp_doc; + } + } + return true; +} + +bool WeatherController::saveWeatherData(QJsonDocument &doc) +{ + QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); + // Ensure the directory exists + QDir dir(cacheDir); + if (!dir.exists()) { + dir.mkpath(cacheDir); + } + + QString fileName = cacheDir + "/" + WeatherController::FILENAME; + + QFile file(fileName); + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&file); + out << doc.toJson(); + file.close(); + qDebug() << "Data saved to hidden cache file."; + return true; + } else { + qDebug() << "Failed to open file for writing"; + return false; + } +} + +bool WeatherController::fetchWeatherData(QUrl url, QJsonDocument &doc) +{ + NetworkManager::Instance()->fetchJSONfromAPI(url, doc); + if (doc.isObject() && !doc.isNull()) { + QJsonObject jsonObject = doc.object(); + + QString todayDate = QDate::currentDate().toString("dd-MM-yyyy"); + + jsonObject["downloaddatum"] = todayDate; + + QString timeOfDownload = QDateTime::currentDateTime().toString("hh:mm:ss"); + + jsonObject["downloadtime"] = timeOfDownload; + + QJsonDocument updatedDoc(jsonObject); + saveWeatherData(updatedDoc); + } + return true; +} + +void WeatherController::updateWeatherData(QJsonDocument &inputDoc) +{ + QJsonDocument doc; + readWeatherData(doc); + QJsonObject rootObj = doc.object(); + QJsonObject inputObj = inputDoc.object(); + QJsonArray liveweerArray = inputObj["liveweer"].toArray(); + rootObj["liveweer"] = liveweerArray; + + QJsonDocument updatedDoc(rootObj); + saveWeatherData(updatedDoc); +} + +QUrl WeatherController::prepareURL(const QString &plaats) +{ + return QUrl(QString("http://data.meteoserver.nl/api/uurverwachting.php?locatie=%1&key=785f0630f0").arg(plaats)); +} + +QUrl WeatherController::prepareURL(const QGeoCoordinate &coord) +{ + return QUrl(QString("http://data.meteoserver.nl/api/liveweer_synop.php?lat=%1&long=%2&key=785f0630f0&select=1").arg(coord.latitude()).arg(coord.longitude())); +} + +bool WeatherController::checkToDownload(QJsonDocument &doc) +{ + bool toDownloadData = false; + readWeatherData(doc); + + if (doc.isObject() && !doc.isNull()) { + + // Get the root object + QJsonObject jsonObject = doc.object(); + + // Extract the 'downloaddatum' value + QString downloaddatum = jsonObject.value("downloaddatum").toString(); + if(downloaddatum != QDate::currentDate().toString("dd-MM-yyyy")) + toDownloadData = true; + + QJsonArray plaatsnaamArray = jsonObject.value("plaatsnaam").toArray(); + + if (!plaatsnaamArray.isEmpty()) { + QJsonObject plaatsObject = plaatsnaamArray.at(0).toObject(); + QString plaats = plaatsObject.value("plaats").toString(); + if(plaats != "Raalte") + toDownloadData = true; + } else { + qWarning() << "plaatsnaam array is empty"; + return false; + } + + } else { + qWarning() << "Failed to open file for reading"; + return false; + } + if(toDownloadData) + return fetchWeatherData(prepareURL("Raalte"), doc); + return false; +} + diff --git a/WeatherRoutes/mvc/controller/weathercontroller.h b/WeatherRoutes/mvc/controller/weathercontroller.h new file mode 100644 index 0000000..2faed60 --- /dev/null +++ b/WeatherRoutes/mvc/controller/weathercontroller.h @@ -0,0 +1,30 @@ +#ifndef WEATHERCONTROLLER_H +#define WEATHERCONTROLLER_H + +#include "../data/weatherdata.h" +#include +#include + +class WeatherController : public QObject { + Q_OBJECT +public: + explicit WeatherController(QObject *parent = nullptr); + + static const QString FILENAME; + bool parseJSONData(); + QJsonObject fetchCurrentWeatherData(const QGeoCoordinate &coord); + +signals: + +private: + bool readWeatherData(QJsonDocument &doc, QString objName = NULL); + bool checkToDownload(QJsonDocument &doc); + bool saveWeatherData(QJsonDocument &doc); + bool fetchWeatherData(QUrl url, QJsonDocument &doc); + void updateWeatherData(QJsonDocument &inputDoc); + QUrl prepareURL(const QString &plaats); + QUrl prepareURL(const QGeoCoordinate &coord); + QList mData; +}; + +#endif // WEATHERCONTROLLER_H diff --git a/WeatherRoutes/mvc/data/mapdata.cpp b/WeatherRoutes/mvc/data/mapdata.cpp new file mode 100644 index 0000000..3b28f26 --- /dev/null +++ b/WeatherRoutes/mvc/data/mapdata.cpp @@ -0,0 +1,42 @@ +#include "mapdata.h" + +MapData::MapData(QObject *parent) : QObject{parent} {} + +QList> MapData::waypoints() const { + return m_waypoints; +} + +void MapData::setWaypoints( + const QList> &newWaypoints) { + if (m_waypoints == newWaypoints) + return; + m_waypoints = newWaypoints; + emit waypointsChanged(); +} + +int MapData::zoomLevel() const { return m_zoomLevel; } + +void MapData::setZoomLevel(int newZoomLevel) { + if (m_zoomLevel == newZoomLevel) + return; + m_zoomLevel = newZoomLevel; + emit zoomLevelChanged(); +} + +int MapData::gpsUpdateInterval() const { return m_gpsUpdateInterval; } + +void MapData::setGpsUpdateInterval(int newGpsUpdateInterval) { + if (m_gpsUpdateInterval == newGpsUpdateInterval) + return; + m_gpsUpdateInterval = newGpsUpdateInterval; + emit gpsUpdateIntervalChanged(); +} + +int MapData::defaultZoomLevel() const { return m_defaultZoomLevel; } + +void MapData::setDefaultZoomLevel(int newDefaultZoomLevel) { + if (m_defaultZoomLevel == newDefaultZoomLevel) + return; + m_defaultZoomLevel = newDefaultZoomLevel; + emit defaultZoomLevelChanged(); +} diff --git a/WeatherRoutes/mvc/data/mapdata.h b/WeatherRoutes/mvc/data/mapdata.h new file mode 100644 index 0000000..4ed2629 --- /dev/null +++ b/WeatherRoutes/mvc/data/mapdata.h @@ -0,0 +1,46 @@ +#ifndef MAPDATA_H +#define MAPDATA_H + +#include "waypoint.h" +#include + +class MapData : public QObject { + Q_OBJECT +public: + explicit MapData(QObject *parent = nullptr); + + Q_PROPERTY(QList> waypoints READ waypoints WRITE + setWaypoints NOTIFY waypointsChanged FINAL) + Q_PROPERTY(int zoomLevel READ zoomLevel WRITE setZoomLevel NOTIFY + zoomLevelChanged FINAL) + Q_PROPERTY(int gpsUpdateInterval READ gpsUpdateInterval WRITE + setGpsUpdateInterval NOTIFY gpsUpdateIntervalChanged FINAL) + Q_PROPERTY(int defaultZoomLevel READ defaultZoomLevel NOTIFY + defaultZoomLevelChanged FINAL) + + QList> waypoints() const; + void setWaypoints(const QList> &newWaypoints); + + int zoomLevel() const; + void setZoomLevel(int newZoomLevel); + + int gpsUpdateInterval() const; + void setGpsUpdateInterval(int newGpsUpdateInterval); + + int defaultZoomLevel() const; + void setDefaultZoomLevel(int newDefaultZoomLevel); + +signals: + void waypointsChanged(); + void zoomLevelChanged(); + void gpsUpdateIntervalChanged(); + void defaultZoomLevelChanged(); + +private: + QList> m_waypoints; + int m_zoomLevel; + int m_gpsUpdateInterval; + int m_defaultZoomLevel; +}; + +#endif // MAPDATA_H diff --git a/WeatherRoutes/mvc/data/waypoint.cpp b/WeatherRoutes/mvc/data/waypoint.cpp new file mode 100644 index 0000000..924cbd9 --- /dev/null +++ b/WeatherRoutes/mvc/data/waypoint.cpp @@ -0,0 +1,16 @@ +#include "waypoint.h" + +Waypoint::Waypoint(QObject *parent) : QObject{parent} {} + +Waypoint::Waypoint(const double latitude, const double longitude) + : m_latitude(latitude), m_longitude(longitude), m_altitude(0) {} + +Waypoint::Waypoint(const double latitude, const double longitude, + WeatherData &data) + : m_latitude(latitude), m_longitude(longitude), m_altitude(0), + m_data(data) {} + +Waypoint::Waypoint(const double latitude, const double longitude, + const double altitude, WeatherData &data) + : m_latitude(latitude), m_longitude(longitude), m_altitude(altitude), + m_data(data) {} diff --git a/WeatherRoutes/mvc/data/waypoint.h b/WeatherRoutes/mvc/data/waypoint.h new file mode 100644 index 0000000..017b6fe --- /dev/null +++ b/WeatherRoutes/mvc/data/waypoint.h @@ -0,0 +1,23 @@ +#ifndef WAYPOINT_H +#define WAYPOINT_H + +#include "weatherdata.h" +#include + +class Waypoint : public QObject { + Q_OBJECT +public: + explicit Waypoint(QObject *parent = nullptr); + Waypoint(const double latitude, const double longitude); + Waypoint(const double latitude, const double longitude, WeatherData &data); + Waypoint(const double latitude, const double longitude, const double altitude, + WeatherData &data); + +private: + double m_latitude; + double m_longitude; + double m_altitude; + WeatherData m_data; +}; + +#endif // WAYPOINT_H diff --git a/WeatherRoutes/mvc/data/weatherdata.cpp b/WeatherRoutes/mvc/data/weatherdata.cpp new file mode 100644 index 0000000..0529db9 --- /dev/null +++ b/WeatherRoutes/mvc/data/weatherdata.cpp @@ -0,0 +1,331 @@ +#include "weatherdata.h" + +WeatherData::WeatherData(QObject *parent) : QObject{parent} {} + +WeatherData::WeatherData(long tijd, const QString &tijd_nl, int offset, + float temp, int wind_ms, int wind_bf, int wind_knp, + int wind_kmh, int wind_r, const QString &wind_ltr, + int visibility, int neersl, float luchtd_bar, + float luchtdmmhg, float luchtdinHg, int hw, int mw, + int lw, int tw, int rv, int gr, int gr_w, + const QString &cape, int snd, int snv, int cond, + int iconCode, const QString &sameenv, + const QString &icoon) + : m_tijd(tijd), m_tijd_nl(tijd_nl), m_offset(offset), m_temp(temp), + m_wind_ms(wind_ms), m_wind_bf(wind_bf), m_wind_knp(wind_knp), + m_wind_kmh(wind_kmh), m_wind_r(wind_r), m_wind_ltr(wind_ltr), + m_visibility(visibility), m_neersl(neersl), m_luchtd_bar(luchtd_bar), + m_luchtdmmhg(luchtdmmhg), m_luchtdinHg(luchtdinHg), m_hw(hw), m_mw(mw), + m_lw(lw), m_tw(tw), m_rv(rv), m_gr(gr), m_gr_w(gr_w), m_cape(cape), + m_snd(snd), m_snv(snv), m_cond(cond), m_iconCode(iconCode), + m_sameenv(sameenv), m_icoon(icoon) {} + +WeatherData::WeatherData(const WeatherData &other) + : m_tijd(other.m_tijd), m_tijd_nl(other.m_tijd_nl), + m_offset(other.m_offset), m_temp(other.m_temp), + m_wind_ms(other.m_wind_ms), m_wind_bf(other.m_wind_bf), + m_wind_knp(other.m_wind_knp), m_wind_kmh(other.m_wind_kmh), + m_wind_r(other.m_wind_r), m_wind_ltr(other.m_wind_ltr), + m_visibility(other.m_visibility), m_neersl(other.m_neersl), + m_luchtd_bar(other.m_luchtd_bar), m_luchtdmmhg(other.m_luchtdmmhg), + m_luchtdinHg(other.m_luchtdinHg), m_hw(other.m_hw), m_mw(other.m_mw), + m_lw(other.m_lw), m_tw(other.m_tw), m_rv(other.m_rv), m_gr(other.m_gr), + m_gr_w(other.m_gr_w), m_cape(other.m_cape), m_snd(other.m_snd), + m_snv(other.m_snv), m_cond(other.m_cond), m_iconCode(other.m_iconCode), + m_sameenv(other.m_sameenv), m_icoon(other.m_icoon) {} + +WeatherData WeatherData::operator=(const WeatherData &other) { + m_tijd = (other.m_tijd); + m_tijd_nl = (other.m_tijd_nl); + m_offset = (other.m_offset); + m_temp = (other.m_temp); + m_wind_ms = (other.m_wind_ms); + m_wind_bf = (other.m_wind_bf); + m_wind_knp = (other.m_wind_knp); + m_wind_kmh = (other.m_wind_kmh); + m_wind_r = (other.m_wind_r); + m_wind_ltr = (other.m_wind_ltr); + m_visibility = (other.m_visibility); + m_neersl = (other.m_neersl); + m_luchtd_bar = (other.m_luchtd_bar); + m_luchtdmmhg = (other.m_luchtdmmhg); + m_luchtdinHg = (other.m_luchtdinHg); + m_hw = (other.m_hw); + m_mw = (other.m_mw); + m_lw = (other.m_lw); + m_tw = (other.m_tw); + m_rv = (other.m_rv); + m_gr = (other.m_gr); + m_gr_w = (other.m_gr_w); + m_cape = (other.m_cape); + m_snd = (other.m_snd); + m_snv = (other.m_snv); + m_cond = (other.m_cond); + m_iconCode = (other.m_iconCode); + m_sameenv = (other.m_sameenv); + m_icoon = (other.m_icoon); + return *this; +} + +long WeatherData::tijd() const { return m_tijd; } + +void WeatherData::setTijd(long newTijd) { + if (m_tijd == newTijd) + return; + m_tijd = newTijd; + emit tijdChanged(); +} + +QString WeatherData::tijd_nl() const { return m_tijd_nl; } + +void WeatherData::setTijd_nl(const QString &newTijd_nl) { + if (m_tijd_nl == newTijd_nl) + return; + m_tijd_nl = newTijd_nl; + emit tijd_nlChanged(); +} + +int WeatherData::offset() const { return m_offset; } + +void WeatherData::setOffset(int newOffset) { + if (m_offset == newOffset) + return; + m_offset = newOffset; + emit offsetChanged(); +} + +float WeatherData::temp() const { return m_temp; } + +void WeatherData::setTemp(float newTemp) { + if (qFuzzyCompare(m_temp, newTemp)) + return; + m_temp = newTemp; + emit tempChanged(); +} + +int WeatherData::wind_ms() const { return m_wind_ms; } + +void WeatherData::setWind_ms(int newWind_ms) { + if (m_wind_ms == newWind_ms) + return; + m_wind_ms = newWind_ms; + emit wind_msChanged(); +} + +int WeatherData::wind_bf() const { return m_wind_bf; } + +void WeatherData::setWind_bf(int newWind_bf) { + if (m_wind_bf == newWind_bf) + return; + m_wind_bf = newWind_bf; + emit wind_bfChanged(); +} + +int WeatherData::wind_knp() const { return m_wind_knp; } + +void WeatherData::setWind_knp(int newWind_knp) { + if (m_wind_knp == newWind_knp) + return; + m_wind_knp = newWind_knp; + emit wind_knpChanged(); +} + +int WeatherData::wind_kmh() const { return m_wind_kmh; } + +void WeatherData::setWind_kmh(int newWind_kmh) { + if (m_wind_kmh == newWind_kmh) + return; + m_wind_kmh = newWind_kmh; + emit wind_kmhChanged(); +} + +int WeatherData::wind_r() const { return m_wind_r; } + +void WeatherData::setWind_r(int newWind_r) { + if (m_wind_r == newWind_r) + return; + m_wind_r = newWind_r; + emit wind_rChanged(); +} + +QString WeatherData::wind_ltr() const { return m_wind_ltr; } + +void WeatherData::setWind_ltr(const QString &newWind_ltr) { + if (m_wind_ltr == newWind_ltr) + return; + m_wind_ltr = newWind_ltr; + emit wind_ltrChanged(); +} + +int WeatherData::visibility() const { return m_visibility; } + +void WeatherData::setVisibility(int newVisibility) { + if (m_visibility == newVisibility) + return; + m_visibility = newVisibility; + emit visibilityChanged(); +} + +int WeatherData::neersl() const { return m_neersl; } + +void WeatherData::setNeersl(int newNeersl) { + if (m_neersl == newNeersl) + return; + m_neersl = newNeersl; + emit neerslChanged(); +} + +float WeatherData::luchtd_bar() const { return m_luchtd_bar; } + +void WeatherData::setLuchtd_bar(float newLuchtd_bar) { + if (qFuzzyCompare(m_luchtd_bar, newLuchtd_bar)) + return; + m_luchtd_bar = newLuchtd_bar; + emit luchtd_barChanged(); +} + +float WeatherData::luchtdmmhg() const { return m_luchtdmmhg; } + +void WeatherData::setLuchtdmmhg(float newLuchtdmmhg) { + if (qFuzzyCompare(m_luchtdmmhg, newLuchtdmmhg)) + return; + m_luchtdmmhg = newLuchtdmmhg; + emit luchtdmmhgChanged(); +} + +float WeatherData::luchtdinHg() const { return m_luchtdinHg; } + +void WeatherData::setLuchtdinHg(float newLuchtdinHg) { + if (qFuzzyCompare(m_luchtdinHg, newLuchtdinHg)) + return; + m_luchtdinHg = newLuchtdinHg; + emit luchtdinHgChanged(); +} + +int WeatherData::hw() const { return m_hw; } + +void WeatherData::setHw(int newHw) { + if (m_hw == newHw) + return; + m_hw = newHw; + emit hwChanged(); +} + +int WeatherData::mw() const { return m_mw; } + +void WeatherData::setMw(int newMw) { + if (m_mw == newMw) + return; + m_mw = newMw; + emit mwChanged(); +} + +int WeatherData::lw() const { return m_lw; } + +void WeatherData::setLw(int newLw) { + if (m_lw == newLw) + return; + m_lw = newLw; + emit lwChanged(); +} + +int WeatherData::tw() const { return m_tw; } + +void WeatherData::setTw(int newTw) { + if (m_tw == newTw) + return; + m_tw = newTw; + emit twChanged(); +} + +int WeatherData::rv() const { return m_rv; } + +void WeatherData::setRv(int newRv) { + if (m_rv == newRv) + return; + m_rv = newRv; + emit rvChanged(); +} + +int WeatherData::gr() const { return m_gr; } + +void WeatherData::setGr(int newGr) { + if (m_gr == newGr) + return; + m_gr = newGr; + emit grChanged(); +} + +int WeatherData::gr_w() const { return m_gr_w; } + +void WeatherData::setGr_w(int newGr_w) { + if (m_gr_w == newGr_w) + return; + m_gr_w = newGr_w; + emit gr_wChanged(); +} + +QString WeatherData::cape() const { return m_cape; } + +void WeatherData::setCape(const QString &newCape) { + if (m_cape == newCape) + return; + m_cape = newCape; + emit capeChanged(); +} + +int WeatherData::snd() const { return m_snd; } + +void WeatherData::setSnd(int newSnd) { + if (m_snd == newSnd) + return; + m_snd = newSnd; + emit sndChanged(); +} + +int WeatherData::snv() const { return m_snv; } + +void WeatherData::setSnv(int newSnv) { + if (m_snv == newSnv) + return; + m_snv = newSnv; + emit snvChanged(); +} + +int WeatherData::cond() const { return m_cond; } + +void WeatherData::setCond(int newCond) { + if (m_cond == newCond) + return; + m_cond = newCond; + emit condChanged(); +} + +int WeatherData::iconCode() const { return m_iconCode; } + +void WeatherData::setIconCode(int newIconCode) { + if (m_iconCode == newIconCode) + return; + m_iconCode = newIconCode; + emit iconCodeChanged(); +} + +QString WeatherData::sameenv() const { return m_sameenv; } + +void WeatherData::setSameenv(const QString &newSameenv) { + if (m_sameenv == newSameenv) + return; + m_sameenv = newSameenv; + emit sameenvChanged(); +} + +QString WeatherData::icoon() const { return m_icoon; } + +void WeatherData::setIcoon(const QString &newIcoon) { + if (m_icoon == newIcoon) + return; + m_icoon = newIcoon; + emit icoonChanged(); +} + +void WeatherData::print() {} diff --git a/WeatherRoutes/mvc/data/weatherdata.h b/WeatherRoutes/mvc/data/weatherdata.h new file mode 100644 index 0000000..f6a1795 --- /dev/null +++ b/WeatherRoutes/mvc/data/weatherdata.h @@ -0,0 +1,306 @@ +#ifndef WEATHERDATA_H +#define WEATHERDATA_H + +#include + +class WeatherData : public QObject { + Q_OBJECT + +public: + enum ICON_NAME { + regen, + bewolkt, + halfbewolkt, + lichtbewolkt, + nachtbewolkt, + wolkennacht, + zonnig, + helderenacht + }; + Q_ENUM(ICON_NAME) + + enum WEATHER_COND { + Regen, + Bewolkt, + Halfbewolkt, + Lichtbewolkt, + Zonnig, + Helder + }; + Q_ENUM(WEATHER_COND) + + enum WIND_DIR { + Noord, + NNO, + NO, + ONO, + Oost, + OZO, + ZO, + ZZO, + Zuid, + ZZW, + ZW, + WZW, + West, + WNW, + NW, + NNW + }; + Q_ENUM(WIND_DIR) + + explicit WeatherData(QObject *parent = nullptr); + + WeatherData(long tijd, const QString &tijd_nl, int offset, float temp, + int wind_ms, int wind_bf, int wind_knp, int wind_kmh, int wind_r, + const QString &wind_ltr, int visibility, int neersl, + float luchtd_bar, float luchtdmmhg, float luchtdinHg, int hw, + int mw, int lw, int tw, int rv, int gr, int gr_w, + const QString &cape, int snd, int snv, int cond, int iconCode, + const QString &sameenv, const QString &icoon); + + WeatherData(const WeatherData &other); + WeatherData operator=(WeatherData const &other); + + Q_PROPERTY(long tijd READ tijd WRITE setTijd NOTIFY tijdChanged + FINAL) // DateTime in Timestamp + Q_PROPERTY(QString tijd_nl READ tijd_nl WRITE setTijd_nl NOTIFY tijd_nlChanged + FINAL) // DateTime is Human readable format + Q_PROPERTY(int offset READ offset WRITE setOffset NOTIFY offsetChanged + FINAL) // Offset is the last reference hour point of the + // model's predicted weather data + Q_PROPERTY(float temp READ temp WRITE setTemp NOTIFY tempChanged + FINAL) // temp in Celsius + Q_PROPERTY(int wind_ms READ wind_ms WRITE setWind_ms NOTIFY wind_msChanged + FINAL) // Wind avg speed in m/s + Q_PROPERTY(int wind_bf READ wind_bf WRITE setWind_bf NOTIFY wind_bfChanged + FINAL) // Wind avg speed in Beaufort + Q_PROPERTY(int wind_knp READ wind_knp WRITE setWind_knp NOTIFY wind_knpChanged + FINAL) // Wind avg speed in knots + Q_PROPERTY(int wind_kmh READ wind_kmh WRITE setWind_kmh NOTIFY wind_kmhChanged + FINAL) // Wind avg speed in k/h + Q_PROPERTY(int wind_r READ wind_r WRITE setWind_r NOTIFY wind_rChanged + FINAL) // Wind direction in degrees + Q_PROPERTY( + QString wind_ltr READ wind_ltr WRITE setWind_ltr NOTIFY wind_ltrChanged + FINAL) // Wind directions in Letters like N,S,E,W + Q_PROPERTY(int visibility READ visibility WRITE setVisibility NOTIFY + visibilityChanged FINAL) // Visibility in meters + Q_PROPERTY(int neersl READ neersl WRITE setNeersl NOTIFY neerslChanged + FINAL) // Precipitation in nml + Q_PROPERTY(float luchtd_bar READ luchtd_bar WRITE setLuchtd_bar NOTIFY + luchtd_barChanged FINAL) // Air pressure in mbar + Q_PROPERTY(float luchtdmmhg READ luchtdmmhg WRITE setLuchtdmmhg NOTIFY + luchtdmmhgChanged FINAL) // Air pressure in mmHg + Q_PROPERTY(float luchtdinHg READ luchtdinHg WRITE setLuchtdinHg NOTIFY + luchtdinHgChanged FINAL) // Air pressure in inHg + Q_PROPERTY(int hw READ hw WRITE setHw NOTIFY hwChanged + FINAL) //% of Higher Cloud desnsity + Q_PROPERTY(int mw READ mw WRITE setMw NOTIFY mwChanged + FINAL) // % of medium cloud density + Q_PROPERTY(int lw READ lw WRITE setLw NOTIFY lwChanged + FINAL) // % of low cloud density + Q_PROPERTY(int tw READ tw WRITE setTw NOTIFY twChanged + FINAL) // % avg of all cloud density + Q_PROPERTY( + int rv READ rv WRITE setRv NOTIFY rvChanged FINAL) // Reletive Humidity + Q_PROPERTY(int gr READ gr WRITE setGr NOTIFY grChanged + FINAL) // Expected Sun radiation J/cm2 + Q_PROPERTY(int gr_w READ gr_w WRITE setGr_w NOTIFY gr_wChanged + FINAL) // Expected Sun radiation watts/m2 + Q_PROPERTY(QString cape READ cape WRITE setCape NOTIFY capeChanged + FINAL) // Not gonna use it + Q_PROPERTY(int snd READ snd WRITE setSnd NOTIFY sndChanged + FINAL) // Available snow deck in mm + Q_PROPERTY(int snv READ snv WRITE setSnv NOTIFY snvChanged + FINAL) // Actual snowfall in mm + Q_PROPERTY(int cond READ cond WRITE setCond NOTIFY condChanged + FINAL) // Cond is condition code is weather type like sunny, + // rainy, etc + Q_PROPERTY(int iconCode READ iconCode WRITE setIconCode NOTIFY iconCodeChanged + FINAL) // Icon code of the weather + Q_PROPERTY(QString sameenv READ sameenv WRITE setSameenv NOTIFY sameenvChanged + FINAL) // Name of the current weather type + Q_PROPERTY(QString icoon READ icoon WRITE setIcoon NOTIFY icoonChanged + FINAL) // Icon name of the current weather + + long tijd() const; + void setTijd(long newTijd); + + QString tijd_nl() const; + void setTijd_nl(const QString &newTijd_nl); + + int offset() const; + void setOffset(int newOffset); + + float temp() const; + void setTemp(float newTemp); + + int wind_ms() const; + void setWind_ms(int newWind_ms); + + int wind_bf() const; + void setWind_bf(int newWind_bf); + + int wind_knp() const; + void setWind_knp(int newWind_knp); + + int wind_kmh() const; + void setWind_kmh(int newWind_kmh); + + int wind_r() const; + void setWind_r(int newWind_r); + + QString wind_ltr() const; + void setWind_ltr(const QString &newWind_ltr); + + int visibility() const; + void setVisibility(int newVisibility); + + int neersl() const; + void setNeersl(int newNeersl); + + float luchtd_bar() const; + void setLuchtd_bar(float newLuchtd_bar); + + float luchtdmmhg() const; + void setLuchtdmmhg(float newLuchtdmmhg); + + float luchtdinHg() const; + void setLuchtdinHg(float newLuchtdinHg); + + int hw() const; + void setHw(int newHw); + + int mw() const; + void setMw(int newMw); + + int lw() const; + void setLw(int newLw); + + int tw() const; + void setTw(int newTw); + + int rv() const; + void setRv(int newRv); + + int gr() const; + void setGr(int newGr); + + int gr_w() const; + void setGr_w(int newGr_w); + + QString cape() const; + void setCape(const QString &newCape); + + int snd() const; + void setSnd(int newSnd); + + int snv() const; + void setSnv(int newSnv); + + int cond() const; + void setCond(int newCond); + + int iconCode() const; + void setIconCode(int newIconCode); + + QString sameenv() const; + void setSameenv(const QString &newSameenv); + + QString icoon() const; + void setIcoon(const QString &newIcoon); + + void print(); + +signals: + void tijdChanged(); + void tijd_nlChanged(); + + void offsetChanged(); + + void tempChanged(); + + void wind_msChanged(); + + void wind_bfChanged(); + + void wind_knpChanged(); + + void wind_kmhChanged(); + + void wind_rChanged(); + + void wind_ltrChanged(); + + void visibilityChanged(); + + void neerslChanged(); + + void luchtd_barChanged(); + + void luchtdmmhgChanged(); + + void luchtdinHgChanged(); + + void hwChanged(); + + void mwChanged(); + + void lwChanged(); + + void twChanged(); + + void rvChanged(); + + void grChanged(); + + void gr_wChanged(); + + void capeChanged(); + + void sndChanged(); + + void snvChanged(); + + void condChanged(); + + void iconCodeChanged(); + + void sameenvChanged(); + + void icoonChanged(); + +private: + long m_tijd; + QString m_tijd_nl; + int m_offset; + float m_temp; + int m_wind_ms; + int m_wind_bf; + int m_wind_knp; + int m_wind_kmh; + int m_wind_r; + QString m_wind_ltr; + int m_visibility; + int m_neersl; + float m_luchtd_bar; + float m_luchtdmmhg; + float m_luchtdinHg; + int m_hw; + int m_mw; + int m_lw; + int m_tw; + int m_rv; + int m_gr; + int m_gr_w; + QString m_cape; + int m_snd; + int m_snv; + int m_cond; + int m_iconCode; + QString m_sameenv; + QString m_icoon; +}; + +#endif // WEATHERDATA_H diff --git a/WeatherRoutes/mvc/data/weatherdetailsdata.cpp b/WeatherRoutes/mvc/data/weatherdetailsdata.cpp new file mode 100644 index 0000000..04e993e --- /dev/null +++ b/WeatherRoutes/mvc/data/weatherdetailsdata.cpp @@ -0,0 +1,84 @@ +#include "weatherdetailsdata.h" +#include +#include + +WeatherDetailsData::WeatherDetailsData(QObject *parent) : QObject{parent} {} + +WeatherDetailsData::WeatherDetailsData(std::shared_ptr &ctrl) + : m_localWeatherCtrl(ctrl) {} + +QString WeatherDetailsData::weatherStatus() const { return m_weatherStatus; } + +void WeatherDetailsData::setWeatherStatus(QString newWeatherStatus) { + if (m_weatherStatus == newWeatherStatus) + return; + m_weatherStatus = newWeatherStatus; + emit weatherStatusChanged(); +} + +double WeatherDetailsData::temperature() const { return m_temperature; } + +void WeatherDetailsData::setTemperature(double newTemperature) { + if (qFuzzyCompare(m_temperature, newTemperature)) + return; + m_temperature = newTemperature; + emit temperatureChanged(); +} + +double WeatherDetailsData::humidity() const { return m_humidity; } + +void WeatherDetailsData::setHumidity(double newHumidity) { + if (qFuzzyCompare(m_humidity, newHumidity)) + return; + m_humidity = newHumidity; + emit humidityChanged(); +} + +double WeatherDetailsData::windSpeed() const { return m_windSpeed; } + +void WeatherDetailsData::setWindSpeed(double newWindSpeed) { + if (qFuzzyCompare(m_windSpeed, newWindSpeed)) + return; + m_windSpeed = newWindSpeed; + emit windSpeedChanged(); +} + +double WeatherDetailsData::precipitation() const { return m_precipitation; } + +void WeatherDetailsData::setPrecipitation(double newPrecipitation) { + if (qFuzzyCompare(m_precipitation, newPrecipitation)) + return; + m_precipitation = newPrecipitation; + emit precipitationChanged(); +} + +void WeatherDetailsData::populate(double temperature, double humidity, + double windSpeed, double precipitation, + QString weatherStatus) { + setTemperature(temperature); + setHumidity(humidity); + setWindSpeed(windSpeed); + setPrecipitation(precipitation); + setWeatherStatus(weatherStatus); +} + +void WeatherDetailsData::populate(QGeoCoordinate coord) { + QJsonObject obj = m_localWeatherCtrl->fetchCurrentWeatherData(coord); + QJsonArray liveweerArray = obj["liveweer"].toArray(); + QJsonObject firstEntry = liveweerArray.at(0).toObject(); + QString temp = verifyForDash(firstEntry["temp"].toString()); + QString humidity = verifyForDash(firstEntry["lv"].toString()); + QString windSpeed = verifyForDash(firstEntry["windkmh"].toString()); + QString precipitation = verifyForDash(firstEntry["neerslagints"].toString()); + QString weatherStatus = verifyForDash(firstEntry["image"].toString()); + populate(temp.toDouble(), humidity.toDouble(), windSpeed.toDouble(), + precipitation.toDouble(), weatherStatus); +} + +QString WeatherDetailsData::getImgSrc(QString status) { + return QString("qrc:/images/live_icons/%1.png").arg(status); +} + +QString WeatherDetailsData::verifyForDash(QString data) { + return data == "-" ? "0" : data; +} diff --git a/WeatherRoutes/mvc/data/weatherdetailsdata.h b/WeatherRoutes/mvc/data/weatherdetailsdata.h new file mode 100644 index 0000000..674d0b8 --- /dev/null +++ b/WeatherRoutes/mvc/data/weatherdetailsdata.h @@ -0,0 +1,81 @@ +#ifndef WEATHERDETAILSDATA_H +#define WEATHERDETAILSDATA_H + +#include +#include "../controller/weathercontroller.h" +#include "enums/weatherstatus.h" +#include + +class WeatherDetailsData : public QObject { + Q_OBJECT +public: + enum LIVE_ICON_NAME { + zonnig, + bliksem, + regen, + buien, + hagel, + mist, + sneeuw, + bewolkt, + halfbewolkt, + zwaarbewolkt, + lichtbewolkt, + nachtmist, + helderenacht, + wolkennacht + }; + Q_ENUM(LIVE_ICON_NAME) + + explicit WeatherDetailsData(QObject *parent = nullptr); + WeatherDetailsData(std::shared_ptr &ctrl); + + Q_PROPERTY(double temperature READ temperature WRITE setTemperature NOTIFY + temperatureChanged FINAL) + Q_PROPERTY(double humidity READ humidity WRITE setHumidity NOTIFY + humidityChanged FINAL) + Q_PROPERTY(double windSpeed READ windSpeed WRITE setWindSpeed NOTIFY + windSpeedChanged FINAL) + Q_PROPERTY(double precipitation READ precipitation WRITE setPrecipitation + NOTIFY precipitationChanged FINAL) + Q_PROPERTY(QString weatherStatus READ weatherStatus WRITE setWeatherStatus + NOTIFY weatherStatusChanged FINAL) + + QString weatherStatus() const; + void setWeatherStatus(QString newWeatherStatus); + + double temperature() const; + void setTemperature(double newTemperature); + + double humidity() const; + void setHumidity(double newHumidity); + + double windSpeed() const; + void setWindSpeed(double newWindSpeed); + + double precipitation() const; + void setPrecipitation(double newPrecipitation); + + void populate(double temperature, double humidity, double windSpeed, + double precipitation, QString weatherStatus); + Q_INVOKABLE void populate(QGeoCoordinate coord); + Q_INVOKABLE QString getImgSrc(QString status); + +signals: + void weatherStatusChanged(); + void temperatureChanged(); + void humidityChanged(); + void windSpeedChanged(); + void precipitationChanged(); + +private: + QString m_weatherStatus = "regen"; + double m_temperature = 0.0; + double m_humidity = 0.0; + double m_windSpeed = 0.0; + double m_precipitation = 0.0; + std::shared_ptr m_localWeatherCtrl; + QString verifyForDash(QString data); +}; + +#endif // WEATHERDETAILSDATA_H diff --git a/WeatherRoutes/mvc/enums/weatherstatus.h b/WeatherRoutes/mvc/enums/weatherstatus.h new file mode 100644 index 0000000..e21c9cc --- /dev/null +++ b/WeatherRoutes/mvc/enums/weatherstatus.h @@ -0,0 +1,20 @@ +#ifndef WEATHERSTATUS_H +#define WEATHERSTATUS_H + +#include + +enum WeatherStatus : uint8_t { + UNKNOWN = 0, + SUNNY, + PARTLY_CLOUDY, + CLOUDY, + RAINY, + SNOWING, + HAILSTORM, + FROZED_TO_DEAD, + THUNDERSTORM, + TSUNAMI + +}; + +#endif // WEATHERSTATUS_H diff --git a/WeatherRoutes/mvc/model/mapdatamodel.cpp b/WeatherRoutes/mvc/model/mapdatamodel.cpp new file mode 100644 index 0000000..30a7bea --- /dev/null +++ b/WeatherRoutes/mvc/model/mapdatamodel.cpp @@ -0,0 +1,34 @@ +// #include "mapdatamodel.h" + +// MapDataModel::MapDataModel(QObject *parent) {} + +// int MapDataModel::rowCount(const QModelIndex &parent) const +// { +// return mDatas.size(); +// } + +// int MapDataModel::columnCount(const QModelIndex &parent) const +// { + +// } + +// QVariant MapDataModel::data(const QModelIndex &index, int role) const +// { +// if (!index.isValid()) +// return QVariant(); +// if ( role == Qt::DisplayRole) +// { +// if ( index.column() == 0) +// return mDatas[index.row()].waypoints(); +// if ( index.column() == 1) +// return mDatas[index.row()].longitude(); +// if ( index.column() == 2) +// return mDatas[index.row()].altitude(); +// } +// return QVariant(); +// } + +// void MapDataModel::populate() +// { + +// } diff --git a/WeatherRoutes/mvc/model/mapdatamodel.h b/WeatherRoutes/mvc/model/mapdatamodel.h new file mode 100644 index 0000000..b126a0e --- /dev/null +++ b/WeatherRoutes/mvc/model/mapdatamodel.h @@ -0,0 +1,22 @@ +// #ifndef MAPDATAMODEL_H +// #define MAPDATAMODEL_H + +// #include +// #include +// #include "../data/mapdata.h" + +// class MapDataModel : public QAbstractItemModel +// { +// Q_OBJECT +// public: +// MapDataModel(QObject * parent = 0); +// int rowCount(const QModelIndex& parent = QModelIndex()) const; +// int columnCount(const QModelIndex& parent = QModelIndex()) const; +// QVariant data(const QModelIndex &index, int role) const; +// void populate(); + +// private: +// QList mDatas; +// }; + +// #endif // MAPDATAMODEL_H diff --git a/WeatherRoutes/qml/CustomInputField.qml b/WeatherRoutes/qml/CustomInputField.qml new file mode 100644 index 0000000..d75bfcd --- /dev/null +++ b/WeatherRoutes/qml/CustomInputField.qml @@ -0,0 +1,50 @@ +import QtQuick +import QtQuick.Controls + +Rectangle { + property alias logoSrc: logoImage.source + property alias placeHolderText: textInput.placeholderText + property alias text: textInput.text + + Rectangle + { + id: logoRect + width: parent.width * 0.1 + height: parent.height + anchors.left: parent.left + anchors.top: parent.top + anchors.verticalCenter: parent.verticalCenter + color: "transparent" + Image + { + id: logoImage + anchors.fill: parent + fillMode: Image.PreserveAspectFit + anchors.verticalCenter: parent.verticalCenter + z: parent.z + 1 + } + } + + Rectangle + { + id: inputRect + width: parent.width * 0.9 + height: parent.height + anchors.left: logoRect.right + anchors.top: parent.top + anchors.verticalCenter: parent.verticalCenter + color: "transparent" + radius: parent.radius + TextField + { + id: textInput + anchors.fill: parent + onPressed: textInput.forceActiveFocus() + background: Rectangle{ + width: inputRect.width + height:inputRect.height + radius: inputRect.radius + } + } + } +} diff --git a/WeatherRoutes/qml/HomeScreen.qml b/WeatherRoutes/qml/HomeScreen.qml new file mode 100644 index 0000000..8a81c38 --- /dev/null +++ b/WeatherRoutes/qml/HomeScreen.qml @@ -0,0 +1,97 @@ +import QtQuick +import QtQuick.Controls + +Rectangle { + id: root + anchors.fill: parent + border.color: "black" + border.width: 2 + color: "transparent" + readonly property int radius: width * 0.025 + + Component.onCompleted: + { + mapView.positionSource.start() + weatherDetailsData.populate(mapView.positionSource.position.coordinate) + } + + CustomInputField + { + id: inputFrom + width: parent.width - (2 * anchors.margins) + height: parent.height * 0.05 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.margins: parent.width * 0.01 + anchors.topMargin: parent.width * 0.02 + logoSrc: "qrc:/images/start_location.svg" + placeHolderText: "Start" + z: mapView.z + 1 + radius: root.radius + } + + CustomInputField + { + id: inputTo + width: parent.width - (2 * anchors.margins) + height: parent.height * 0.05 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: inputFrom.bottom + anchors.margins: parent.width * 0.01 + logoSrc: "qrc:/images/Destination_location.png" + placeHolderText: "Destination" + z: mapView.z + 1 + radius: root.radius + } + + MapView + { + id: mapView + anchors.fill: parent + radius: root.radius + } + + Button + { + id: myLocationButton + width: parent.width * 0.1 + height: width + anchors.right: parent.right + anchors.bottom: weatherDetails.top + anchors.margins: parent.width * 0.01 + background: Rectangle + { + anchors.fill: parent + color:"darkgray" + radius: width/2 + Image + { + source: "qrc:/images/centerToGPS.png" + fillMode: Image.PreserveAspectFit + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + onClicked: + { + mapView.resetZoomLevel() + } + z: mapView.z + 1 + } + + WeatherDetailsView + { + id: weatherDetails + width: parent.width + height: parent.height * 0.1 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + z: mapView.z + 1 + radius: root.radius + imgSrc: weatherDetailsData.getImgSrc(weatherDetailsData.weatherStatus) + temperature: weatherDetailsData.temperature + precipitation: weatherDetailsData.precipitation + windSpeed: weatherDetailsData.windSpeed + humidity: weatherDetailsData.humidity + } +} diff --git a/WeatherRoutes/qml/MapView.qml b/WeatherRoutes/qml/MapView.qml new file mode 100644 index 0000000..e734eae --- /dev/null +++ b/WeatherRoutes/qml/MapView.qml @@ -0,0 +1,107 @@ +import QtQuick +import QtLocation +import QtPositioning + +Rectangle { + property alias positionSource: positionSource + property variant waypoints: mapData.waypoints + + function resetZoomLevel() + { + map.zoomLevel = mapData.defaultZoomLevel + map.bearing = 0; + positionSource.start() + } + + Plugin { + id: mapPlugin + name: "osm" + PluginParameter { + name: "osm.mapping.providersrepository.disabled" + value: "true" + } + PluginParameter { + name: "osm.mapping.providersrepository.address" + value: "http://maps-redirect.qt.io/osm/5.6/" + } + } + + PositionSource + { + id: positionSource + updateInterval: mapData.gpsUpdateInterval + active: true + onPositionChanged: { + var coord = positionSource.position.coordinate; + map.visible = true; + map.center = positionSource.position.coordinate; + } + } + + MapQuickItem { + id: marker + zoomLevel: map.zoomLevel + anchorPoint.x: image.width/2 + anchorPoint.y: image.height + coordinate: positionSource.position.coordinate + + sourceItem: Image { + id: image + height: 40 + width: height*2/3 + source: "qrc:/images/currentLocationOfWaypoint.svg" + transform: Rotation { origin.x: marker.anchorPoint.x; origin.y: marker.anchorPoint.y; angle: map.bearing} + } + Component.onCompleted: map.addMapItem(marker) + } + + Map { + id: map + anchors.fill: parent + plugin: mapPlugin + center: QtPositioning.coordinate(59.91, 10.75) // Oslo + zoomLevel: mapData.zoomLevel + property geoCoordinate startCentroid + + PinchHandler { + id: pinch + target: null + onActiveChanged: if (active) { + map.startCentroid = map.toCoordinate(pinch.centroid.position, false) + } + onScaleChanged: (delta) => { + map.zoomLevel += Math.log2(delta) + map.alignCoordinateToPoint(map.startCentroid, pinch.centroid.position) + } + onRotationChanged: (delta) => { + map.bearing -= delta + map.alignCoordinateToPoint(map.startCentroid, pinch.centroid.position) + } + grabPermissions: PointerHandler.TakeOverForbidden + } + WheelHandler { + id: wheel + acceptedDevices: Qt.platform.pluginName === "cocoa" || Qt.platform.pluginName === "wayland" + ? PointerDevice.Mouse | PointerDevice.TouchPad + : PointerDevice.Mouse + rotationScale: 1/120 + property: "zoomLevel" + } + DragHandler { + id: drag + target: null + onTranslationChanged: (delta) => map.pan(-delta.x, -delta.y) + // onGrabChanged: positionSource.active = false + } + Shortcut { + enabled: map.zoomLevel < map.maximumZoomLevel + sequence: StandardKey.ZoomIn + onActivated: map.zoomLevel = Math.round(map.zoomLevel + 1) + } + Shortcut { + enabled: map.zoomLevel > map.minimumZoomLevel + sequence: StandardKey.ZoomOut + onActivated: map.zoomLevel = Math.round(map.zoomLevel - 1) + } + } +} diff --git a/WeatherRoutes/qml/WaypointData.qml b/WeatherRoutes/qml/WaypointData.qml new file mode 100644 index 0000000..68c2108 --- /dev/null +++ b/WeatherRoutes/qml/WaypointData.qml @@ -0,0 +1,5 @@ +import QtQuick 2.15 + +Item { + +} diff --git a/WeatherRoutes/qml/WeatherDetailsView.qml b/WeatherRoutes/qml/WeatherDetailsView.qml new file mode 100644 index 0000000..44c55ba --- /dev/null +++ b/WeatherRoutes/qml/WeatherDetailsView.qml @@ -0,0 +1,119 @@ +import QtQuick + +Rectangle { + id: root + property alias imgSrc: image.source + property alias temperature: tempValue.text + property alias humidity: humidityValue.text + property alias precipitation: precipValue.text + property alias windSpeed: windValue.text + + property double rightMargin: root.width * 0.05 + + Rectangle + { + id: imageRect + width: parent.width * 0.2 + height: parent.height + anchors.left: parent.left + anchors.top: parent.top + radius: 15 + color: "black" + Image + { + id: image + fillMode: Image.PreserveAspectFit + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + Rectangle + { + id: tempRect + width: parent.width * 0.4 + height: parent.height * 0.5 + anchors.left: imageRect.right + anchors.top: parent.top + Text + { + id: tempLabel + text: "Temp (C):" + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + } + Text + { + id: tempValue + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: root.rightMargin + } + } + Rectangle + { + id: humidityRect + width: parent.width * 0.4 + height: parent.height * 0.5 + anchors.left: tempRect.right + anchors.top: parent.top + Text + { + id: humidityLabel + text: "Humidity (%):" + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + } + Text + { + id: humidityValue + anchors.right: parent.right + anchors.rightMargin: root.rightMargin + anchors.verticalCenter: parent.verticalCenter + } + } + Rectangle + { + id: precipRect + width: parent.width * 0.4 + height: parent.height * 0.5 + anchors.left: imageRect.right + anchors.top: tempRect.bottom + Text + { + id: precipLabel + text: "Precipitation (mm)" + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + + } + Text + { + id: precipValue + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: root.rightMargin + } + } + Rectangle + { + id: windRect + width: parent.width * 0.4 + height: parent.height * 0.5 + anchors.left: tempRect.right + anchors.top: humidityRect.bottom + Text + { + id: windLabel + text: "Wind (km/h):" + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + } + Text + { + id: windValue + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: root.rightMargin + } + } +} diff --git a/WeatherRoutes/resources.qrc b/WeatherRoutes/resources.qrc new file mode 100644 index 0000000..5aa5ed1 --- /dev/null +++ b/WeatherRoutes/resources.qrc @@ -0,0 +1,28 @@ + + + images/Destination_location.png + images/start_location.png + images/weather_partly_cloudy_day.png + images/weather_rainy.png + images/weather_sunny.png + images/weather_thunderstorm.png + images/weather_tsunami.png + images/start_location.svg + images/centerToGPS.png + images/currentLocationOfWaypoint.svg + images/live_icons/bewolkt.png + images/live_icons/bliksem.png + images/live_icons/buien.png + images/live_icons/hagel.png + images/live_icons/halfbewolkt.png + images/live_icons/helderenacht.png + images/live_icons/lichtbewolkt.png + images/live_icons/mist.png + images/live_icons/nachtmist.png + images/live_icons/regen.png + images/live_icons/sneeuw.png + images/live_icons/wolkennacht.png + images/live_icons/zonnig.png + images/live_icons/zwaarbewolkt.png + +