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"