File indexing completed on 2024-11-24 04:15:37

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <config-kosmindoormap.h>
0008 #include "mapdata.h"
0009 #include "levelparser_p.h"
0010 
0011 #if !BUILD_TOOLS_ONLY
0012 #include "style/mapcssdeclaration_p.h"
0013 #include "style/mapcssresult.h"
0014 #include "style/mapcssstate_p.h"
0015 
0016 #include <KOSMIndoorMap/MapCSSParser>
0017 #include <KOSMIndoorMap/MapCSSProperty>
0018 #include <KOSMIndoorMap/MapCSSStyle>
0019 #endif
0020 
0021 #include <osm/geomath.h>
0022 
0023 #include <QPointF>
0024 #include <QTimeZone>
0025 
0026 using namespace KOSMIndoorMap;
0027 
0028 MapLevel::MapLevel(int level)
0029     : m_level(level)
0030 {
0031 }
0032 
0033 MapLevel::~MapLevel() = default;
0034 
0035 bool MapLevel::operator<(const MapLevel &other) const
0036 {
0037     return m_level > other.m_level;
0038 }
0039 
0040 bool MapLevel::operator==(const MapLevel &other) const
0041 {
0042     return m_level == other.m_level;
0043 }
0044 
0045 bool MapLevel::hasName() const
0046 {
0047     return !m_levelName.isEmpty();
0048 }
0049 
0050 QString MapLevel::name() const
0051 {
0052     if (m_levelName.isEmpty()) {
0053         return QString::number(m_level / 10);
0054     }
0055     return m_levelName;
0056 }
0057 
0058 void MapLevel::setName(const QString &name)
0059 {
0060     m_levelName = name;
0061 }
0062 
0063 bool MapLevel::isFullLevel() const
0064 {
0065     return m_level % 10 == 0;
0066 }
0067 
0068 int MapLevel::fullLevelBelow() const
0069 {
0070     return m_level < 0 ? (m_level - (10 + m_level % 10)) : (m_level - m_level % 10);
0071 }
0072 
0073 int MapLevel::fullLevelAbove() const
0074 {
0075     return m_level < 0 ? (m_level - m_level % 10) : (m_level + (10 - m_level % 10));
0076 }
0077 
0078 int MapLevel::numericLevel() const
0079 {
0080     return m_level;
0081 }
0082 
0083 namespace KOSMIndoorMap {
0084 class MapDataPrivate {
0085 public:
0086     OSM::DataSet m_dataSet;
0087     OSM::BoundingBox m_bbox;
0088 
0089     OSM::TagKey m_levelRefTag;
0090     OSM::TagKey m_nameTag;
0091 
0092     std::map<MapLevel, std::vector<OSM::Element>> m_levelMap;
0093     std::map<MapLevel, std::size_t> m_dependentElementCounts;
0094 
0095     QString m_regionCode;
0096     QTimeZone m_timeZone;
0097 };
0098 }
0099 
0100 MapData::MapData()
0101     : d(std::make_shared<MapDataPrivate>())
0102 {
0103 }
0104 
0105 MapData::MapData(const MapData&) = default;
0106 MapData::MapData(MapData&&) = default;
0107 MapData::~MapData() = default;
0108 
0109 MapData& MapData::operator=(const MapData&) = default;
0110 MapData& MapData::operator=(MapData&&) = default;
0111 
0112 const OSM::DataSet& MapData::dataSet() const
0113 {
0114     return d->m_dataSet;
0115 }
0116 
0117 bool MapData::isEmpty() const
0118 {
0119     return !d || d->m_levelMap.empty();
0120 }
0121 
0122 bool MapData::operator==(const MapData &other) const
0123 {
0124     return d == other.d;
0125 }
0126 
0127 OSM::DataSet& MapData::dataSet()
0128 {
0129     return d->m_dataSet;
0130 }
0131 
0132 void MapData::setDataSet(OSM::DataSet &&dataSet)
0133 {
0134     d->m_dataSet = std::move(dataSet);
0135 
0136     d->m_levelRefTag = d->m_dataSet.tagKey("level:ref");
0137     d->m_nameTag = d->m_dataSet.tagKey("name");
0138 
0139     d->m_levelMap.clear();
0140     d->m_bbox = {};
0141 
0142     processElements();
0143     filterLevels();
0144 }
0145 
0146 OSM::BoundingBox MapData::boundingBox() const
0147 {
0148     return d->m_bbox;
0149 }
0150 
0151 void MapData::setBoundingBox(OSM::BoundingBox bbox)
0152 {
0153     d->m_bbox = bbox;
0154 }
0155 
0156 const std::map<MapLevel, std::vector<OSM::Element>>& MapData::levelMap() const
0157 {
0158     return d->m_levelMap;
0159 }
0160 
0161 void MapData::processElements()
0162 {
0163     const auto levelTag = d->m_dataSet.tagKey("level");
0164     const auto repeatOnTag = d->m_dataSet.tagKey("repeat_on");
0165     const auto buildingLevelsTag = d->m_dataSet.tagKey("building:levels");
0166     const auto buildingMinLevelTag = d->m_dataSet.tagKey("building:min_level");
0167     const auto buildingLevelsUndergroundTag = d->m_dataSet.tagKey("building:levels:underground");
0168     const auto maxLevelTag = d->m_dataSet.tagKey("max_level");
0169     const auto minLevelTag = d->m_dataSet.tagKey("min_level");
0170     const auto countryTag = d->m_dataSet.tagKey("addr:country");
0171 
0172 #if !BUILD_TOOLS_ONLY
0173     MapCSSParser p;
0174     auto filter = p.parse(QStringLiteral(":/org.kde.kosmindoormap/assets/css/input-filter.mapcss"));
0175     if (p.hasError()) {
0176         qWarning() << p.errorMessage();
0177     }
0178     filter.compile(d->m_dataSet);
0179     MapCSSResult filterResult;
0180 #endif
0181 
0182     OSM::for_each(d->m_dataSet, [&](auto e) {
0183         // discard everything here that is tag-less (and thus likely part of a higher-level geometry)
0184         if (!e.hasTags()) {
0185             return;
0186         }
0187 
0188         // attempt to detect the country we are in
0189         if (d->m_regionCode.isEmpty()) {
0190             const auto countryCode = e.tagValue(countryTag);
0191             if (countryCode.size() == 2 && std::isupper(static_cast<unsigned char>(countryCode[0])) && std::isupper(static_cast<unsigned char>(countryCode[1]))) {
0192                 d->m_regionCode = QString::fromUtf8(countryCode);
0193             }
0194         }
0195 
0196         // apply the input filter, anything that explicitly got opacity 0 will be discarded
0197         bool isDependentElement = false;
0198 #if !BUILD_TOOLS_ONLY
0199         MapCSSState filterState;
0200         filterState.element = e;
0201         filter.evaluate(std::move(filterState), filterResult);
0202         if (auto prop = filterResult[{}].declaration(MapCSSProperty::Opacity)) {
0203             if (prop->doubleValue() == 0.0) {
0204                 qDebug() << "input filter dropped" << e.url();
0205                 return;
0206             }
0207             // anything that doesn't work on its own is a "dependent element"
0208             // we discard levels only containing dependent elements, but we retain all of them if the
0209             // level contains an element we are sure about that we can display it
0210             if (prop->doubleValue() < 1.0) {
0211                 isDependentElement = true;
0212             }
0213         }
0214 #endif
0215 
0216         // bbox computation
0217         e.recomputeBoundingBox(d->m_dataSet);
0218         d->m_bbox = OSM::unite(e.boundingBox(), d->m_bbox);
0219 
0220         // multi-level building element
0221         // we handle this first, before level=, as level is often used instead
0222         // of building:min_level in combination with building:level
0223         const auto buildingLevels = e.tagValue(buildingLevelsTag, maxLevelTag).toInt();
0224         if (buildingLevels > 0) {
0225             const auto startLevel = e.tagValue(buildingMinLevelTag, levelTag, minLevelTag).toInt();
0226             //qDebug() << startLevel << buildingLevels << e.url();
0227             for (auto i = startLevel; i < startLevel + buildingLevels; ++i) {
0228                 addElement(i * 10, e, true);
0229             }
0230         }
0231         const auto undergroundLevels = e.tagValue(buildingLevelsUndergroundTag).toUInt();
0232         for (auto i = undergroundLevels; i > 0; --i) {
0233             addElement(-i * 10, e, true);
0234         }
0235         if (buildingLevels > 0 || undergroundLevels > 0) {
0236             return;
0237         }
0238 
0239         // element with explicit level specified
0240         auto level = e.tagValue(levelTag);
0241         if (level.isEmpty()) {
0242             level = e.tagValue(repeatOnTag);
0243         }
0244         if (!level.isEmpty()) { // level-less -> outdoor
0245             LevelParser::parse(std::move(level), e, [this, isDependentElement](int level, OSM::Element e) {
0246                 addElement(level, e, isDependentElement);
0247             });
0248             return;
0249         }
0250 
0251         // no level information available
0252         d->m_levelMap[MapLevel{}].push_back(e);
0253         if (isDependentElement) {
0254             d->m_dependentElementCounts[MapLevel{}]++;
0255         }
0256     });
0257 }
0258 
0259 void MapData::addElement(int level, OSM::Element e, bool isDependentElement)
0260 {
0261     MapLevel l(level);
0262     auto it = d->m_levelMap.find(l);
0263     if (it == d->m_levelMap.end()) {
0264         l.setName(levelName(e));
0265         d->m_levelMap[l] = {e};
0266     } else {
0267         if (!(*it).first.hasName()) {
0268             // name does not influence op< behavior, so modifying the key here is safe
0269             const_cast<MapLevel&>((*it).first).setName(levelName(e));
0270         }
0271         (*it).second.push_back(e);
0272     }
0273     if (isDependentElement) {
0274         d->m_dependentElementCounts[l]++;
0275     }
0276 }
0277 
0278 static bool isPlausibleLevelName(const QByteArray &s)
0279 {
0280     return !s.isEmpty() && !s.contains(';');
0281 }
0282 
0283 QString MapData::levelName(OSM::Element e)
0284 {
0285     const auto n = e.tagValue(d->m_levelRefTag);
0286     if (isPlausibleLevelName(n)) {
0287         return QString::fromUtf8(n);
0288     }
0289 
0290     if (e.type() == OSM::Type::Relation) {
0291         const auto isLevelRel = std::all_of(e.relation()->members.begin(), e.relation()->members.end(), [](const auto &mem) {
0292             return std::strcmp(mem.role().name(), "shell") == 0 || std::strcmp(mem.role().name(), "buildingpart") == 0;
0293         });
0294         if (isLevelRel) {
0295             const auto n = e.tagValue(d->m_nameTag);
0296             if (isPlausibleLevelName(n)) {
0297                 return QString::fromUtf8(n);
0298             }
0299         }
0300     }
0301 
0302     return {};
0303 }
0304 
0305 void MapData::filterLevels()
0306 {
0307     // remove all levels that don't contain something we are sure would make a meaningful output
0308     // always retain the base level though
0309     for (auto it = d->m_levelMap.begin(); it != d->m_levelMap.end();) {
0310         if ((*it).first.numericLevel() != 0 && d->m_dependentElementCounts[(*it).first] == (*it).second.size()) {
0311             it = d->m_levelMap.erase(it);
0312         } else {
0313             ++it;
0314         }
0315     }
0316     d->m_dependentElementCounts.clear();
0317 }
0318 
0319 QPointF MapData::center() const
0320 {
0321     return QPointF(d->m_bbox.center().lonF(), d->m_bbox.center().latF());
0322 }
0323 
0324 float MapData::radius() const
0325 {
0326     return std::max(OSM::distance(d->m_bbox.center(), d->m_bbox.min), OSM::distance(d->m_bbox.center(), d->m_bbox.max));
0327 }
0328 
0329 QString MapData::regionCode() const
0330 {
0331     return d->m_regionCode;
0332 }
0333 
0334 void MapData::setRegionCode(const QString &regionCode)
0335 {
0336     d->m_regionCode = regionCode;
0337 }
0338 
0339 QTimeZone MapData::timeZone() const
0340 {
0341     return d->m_timeZone;
0342 }
0343 
0344 void MapData::setTimeZone(const QTimeZone &tz)
0345 {
0346     d->m_timeZone = tz;
0347 }
0348 
0349 QString MapData::timeZoneId() const
0350 {
0351     return QString::fromUtf8(d->m_timeZone.id());
0352 }
0353 
0354 #include "moc_mapdata.cpp"