File indexing completed on 2024-05-12 04:42:42

0001 /*
0002     SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "coveragearea.h"
0008 #include "datatypes_p.h"
0009 #include "json_p.h"
0010 #include "location.h"
0011 #include "logging.h"
0012 #include "geo/geojson_p.h"
0013 
0014 #include <QFile>
0015 #include <QJsonDocument>
0016 #include <QJsonObject>
0017 #include <QPolygonF>
0018 
0019 using namespace KPublicTransport;
0020 
0021 namespace KPublicTransport {
0022 class CoverageAreaPrivate : public QSharedData {
0023 public:
0024     void loadGeometry();
0025     void recomputeBoundingBox();
0026 
0027     CoverageArea::Type type = CoverageArea::Any;
0028     QStringList regions;
0029     QStringList uicCompanyCodes;
0030     QStringList vdvOrganizationIds;
0031     QString areaFile;
0032     std::vector<QPolygonF> areas;
0033     QRectF boundingBox;
0034 };
0035 }
0036 
0037 void CoverageAreaPrivate::loadGeometry()
0038 {
0039     if (areaFile.isEmpty() || !areas.empty()) {
0040         return;
0041     }
0042 
0043     QFile f(QLatin1String(":/org.kde.kpublictransport/networks/geometry/") + areaFile);
0044     if (!f.open(QFile::ReadOnly)) {
0045         qCWarning(Log) << "reading coverage area file failed:" << f.fileName() << f.errorString();
0046         return;
0047     }
0048 
0049     const auto doc = QJsonDocument::fromJson(f.readAll());
0050     areas = GeoJson::readOuterPolygons(doc.object());
0051     recomputeBoundingBox();
0052 }
0053 
0054 void CoverageAreaPrivate::recomputeBoundingBox()
0055 {
0056     for (const auto &area : areas) {
0057         boundingBox |= area.boundingRect();
0058     }
0059 }
0060 
0061 KPUBLICTRANSPORT_MAKE_GADGET(CoverageArea)
0062 KPUBLICTRANSPORT_MAKE_PROPERTY(CoverageArea, CoverageArea::Type, type, setType)
0063 KPUBLICTRANSPORT_MAKE_PROPERTY(CoverageArea, QStringList, regions, setRegions)
0064 KPUBLICTRANSPORT_MAKE_PROPERTY(CoverageArea, QStringList, uicCompanyCodes, setUicCompanyCodes)
0065 KPUBLICTRANSPORT_MAKE_PROPERTY(CoverageArea, QStringList, vdvOrganizationIds, setVdvOrganizationIds)
0066 
0067 bool CoverageArea::isEmpty() const
0068 {
0069     return d->regions.isEmpty() && d->areas.empty();
0070 }
0071 
0072 bool CoverageArea::isGlobal() const
0073 {
0074     if (d->regions.size() == 1 && d->regions.at(0) == QLatin1String("UN")) {
0075         return true;
0076     }
0077 
0078     return d->boundingBox.topLeft() == QPointF(-180.0, -90.0) && d->boundingBox.bottomRight() == QPointF(180.0, 90.0);
0079 }
0080 
0081 static QStringView countryCode(QStringView isoCode)
0082 {
0083     return isoCode.size() < 2 ? QStringView() : isoCode.left(2);
0084 }
0085 
0086 bool CoverageArea::coversLocation(const Location &loc) const
0087 {
0088     if (loc.hasCoordinate()) {
0089         d->loadGeometry();
0090         if (!d->areas.empty()) {
0091             if (d->boundingBox.contains({loc.longitude(), loc.latitude()})) {
0092                 return std::any_of(d->areas.begin(), d->areas.end(), [&loc](const auto &area) {
0093                     return area.containsPoint({loc.longitude(), loc.latitude()}, Qt::WindingFill);
0094                 });
0095             }
0096             return false;
0097         }
0098     }
0099 
0100     // TODO we could do a more precise check for ISO 3166-2 subdivision codes when available
0101 
0102     if (loc.country().size() == 2 && !d->regions.empty()) {
0103         if (d->regions.size() == 1 && d->regions.at(0) == QLatin1String("UN")) { // global coverage
0104             return true;
0105         }
0106         return std::binary_search(d->regions.begin(), d->regions.end(), loc.country(), [](const auto &lhs, const auto &rhs) {
0107             return countryCode(lhs) < countryCode(rhs);
0108         });
0109     }
0110 
0111     // if we have nothing to check against, assume the backend can help (otherwise none would trigger)
0112     return true;
0113 }
0114 
0115 bool CoverageArea::hasNationWideCoverage(const QString &country) const
0116 {
0117     return std::binary_search(d->regions.begin(), d->regions.end(), country);
0118 }
0119 
0120 CoverageArea CoverageArea::fromJson(const QJsonObject &obj)
0121 {
0122     CoverageArea ca;
0123     ca.setRegions(Json::toStringList(obj.value(QLatin1String("region"))));
0124     ca.setUicCompanyCodes(Json::toStringList(obj.value(QLatin1String("uicCompanyCodes"))));
0125     std::sort(ca.d->regions.begin(), ca.d->regions.end());
0126 
0127     ca.d->areaFile = obj.value(QLatin1String("areaFile")).toString();
0128     if (ca.d->areaFile.isEmpty()) {
0129         ca.d->areas = GeoJson::readOuterPolygons(obj.value(QLatin1String("area")).toObject());
0130         ca.d->recomputeBoundingBox();
0131     }
0132     return ca;
0133 }
0134 
0135 #include "moc_coveragearea.cpp"