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 ®ionCode) 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"