diff --git a/src/map/CMakeLists.txt b/src/map/CMakeLists.txt index e95e108..0119220 100644 --- a/src/map/CMakeLists.txt +++ b/src/map/CMakeLists.txt @@ -1,81 +1,82 @@ flex_target(mapcssscanner style/mapcsslexer.l ${CMAKE_CURRENT_BINARY_DIR}/mapcsslexer.cpp DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/mapcssscanner.h COMPILE_FLAGS "--nounistd" ) bison_target(mapcssparser style/mapcssparser.y ${CMAKE_CURRENT_BINARY_DIR}/mapcssparser_p.cpp DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/mapcssparser_p.h ) add_flex_bison_dependency(mapcssscanner mapcssparser) add_library(KOSMIndoorMap STATIC assets/assets.qrc + loader/boundarysearch.cpp loader/mapdata.cpp loader/maploader.cpp loader/tilecache.cpp renderer/hitdetector.cpp renderer/painterrenderer.cpp renderer/view.cpp scene/scenecontroller.cpp scene/scenegeometry.cpp scene/scenegraph.cpp scene/scenegraphitem.cpp style/mapcsscondition.cpp style/mapcssdeclaration.cpp style/mapcssparser.cpp style/mapcssresult.cpp style/mapcssrule.cpp style/mapcssselector.cpp style/mapcssstate.cpp style/mapcssstyle.cpp ${BISON_mapcssparser_OUTPUTS} ${FLEX_mapcssscanner_OUTPUTS} ) target_include_directories(KOSMIndoorMap PRIVATE $) target_include_directories(KOSMIndoorMap PUBLIC $) target_link_libraries(KOSMIndoorMap PUBLIC Qt5::Gui KOSM PRIVATE Qt5::Network ) ecm_generate_headers(KOSMIndoorMap_Loader_FORWARDING_HEADERS HEADER_NAMES MapLoader MapData PREFIX KOSMIndoorMap REQUIRED_HEADERS KOSMIndoorMap_Loader_HEADERS RELATIVE loader ) ecm_generate_headers(KOSMIndoorMap_Renderer_FORWARDING_HEADERS HEADER_NAMES HitDetector PainterRenderer View PREFIX KOSMIndoorMap REQUIRED_HEADERS KOSMIndoorMap_Renderer_HEADERS RELATIVE renderer ) ecm_generate_headers(KOSMIndoorMap_Scene_FORWARDING_HEADERS HEADER_NAMES SceneController SceneGraph PREFIX KOSMIndoorMap REQUIRED_HEADERS KOSMIndoorMap_Scene_HEADERS RELATIVE scene ) ecm_generate_headers(KOSMIndoorMap_Style_FORWARDING_HEADERS HEADER_NAMES MapCSSParser MapCSSStyle PREFIX KOSMIndoorMap REQUIRED_HEADERS KOSMIndoorMap_Style_HEADERS RELATIVE style ) diff --git a/src/map/loader/boundarysearch.cpp b/src/map/loader/boundarysearch.cpp new file mode 100644 index 0000000..8856dee --- /dev/null +++ b/src/map/loader/boundarysearch.cpp @@ -0,0 +1,117 @@ +/* + Copyright (C) 2020 Volker Krause + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "boundarysearch.h" + +#include +#include +#include + +#include + +using namespace KOSMIndoorMap; + +enum { + BoundingBoxMargin = 100, // margin around the final result, in meters + BoundingBoxMaxSize = 2000, // maximum width/height of the bounding box, in meters +}; + +void BoundarySearch::init(OSM::Coordinate coord) +{ + m_center = coord; + + m_bbox = {coord, coord}; + m_relevantIds.clear(); +} + +static OSM::Id actualId(OSM::Element e, OSM::TagKey mxoidTag) +{ + const auto mxoid = e.tagValue(mxoidTag); + if (!mxoid.isEmpty()) { + return mxoid.toLongLong(); + } + return e.id(); +} + +/* There's a number of critieria being considered here: + * - a certain minimum radius around center (see BoundingBoxMargin) + * - an upper limit (BoundingBoxMaxSize), to avoid this growing out of control + * - buildings, stations or airports containing the center position + * -- for now with manual geometry re-assmbly from Marble vector tiles, ideally this will happen in a general step beforehand + * - relevant elements (e.g. platforms or terminal buildings) in the vicinity of center (TODO) + */ +OSM::BoundingBox BoundarySearch::boundingBox(const OSM::DataSet& dataSet) +{ + // cache tag keys for fast lookup + const auto buildingTag = dataSet.tagKey("building"); + const auto railwayTag = dataSet.tagKey("railway"); + const auto aerowayTag = dataSet.tagKey("aeroway"); + const auto mxoidTag = dataSet.tagKey("mx:oid"); + + if (m_relevantIds.empty()) { // first pass over the center tile + OSM::for_each(dataSet, [&](OSM::Element e) { + const bool isRelevant = !e.tagValue(buildingTag).isEmpty() + || !e.tagValue(railwayTag).isEmpty() + || !e.tagValue(aerowayTag).isEmpty(); + + if (!isRelevant) { + return; + } + if (!e.boundingBox().isValid()) { + e.recomputeBoundingBox(dataSet); + } + + m_relevantIds.insert(actualId(e, mxoidTag)); + m_bbox = OSM::unite(m_bbox, e.boundingBox()); + }, OSM::IncludeRelations | OSM::IncludeWays); + } + + OSM::BoundingBox bbox = m_bbox; + OSM::for_each(dataSet, [&](OSM::Element e) { + // TODO cache the remaining tag keys here too + const bool isStation = (e.tagValue(railwayTag) == QLatin1String("station")) || (e.tagValue(buildingTag) == QLatin1String("train_station") || e.tagValue("public_transport") == QLatin1String("platform")); + const bool isAirport = (e.tagValue(aerowayTag) == QLatin1String("aerodrome")); + if (!isStation && !isAirport) { + return; + } + if (!e.boundingBox().isValid()) { + e.recomputeBoundingBox(dataSet); + } + if (OSM::intersects(e.boundingBox(), m_bbox) || m_relevantIds.count(actualId(e, mxoidTag))) { + bbox = OSM::unite(bbox, e.boundingBox()); + } + }, OSM::IncludeRelations | OSM::IncludeWays); + + return clampBoundingBox(growBoundingBox(bbox, BoundingBoxMargin), BoundingBoxMaxSize); +} + +OSM::BoundingBox BoundarySearch::growBoundingBox(const OSM::BoundingBox &bbox, double meters) const +{ + const auto dlon = meters / OSM::distance(m_center.latF(), 0.0, m_center.latF(), 1.0); + const auto dlat = meters / OSM::distance(0.0, m_center.lonF(), 1.0, m_center.lonF()); + return OSM::BoundingBox(OSM::Coordinate(bbox.min.latF() - dlat, bbox.min.lonF() - dlon), + OSM::Coordinate(bbox.max.latF() + dlat, bbox.max.lonF() + dlon)); +} + +OSM::BoundingBox BoundarySearch::clampBoundingBox(const OSM::BoundingBox &bbox, double meters) const +{ + // TODO don'T do this around the bbox center, but biased towards m_center + const auto dlon = std::max(0.0, bbox.widthF() - (meters / OSM::distance(m_center.latF(), 0.0, m_center.latF(), 1.0))) / 2.0; + const auto dlat = std::max(0.0, bbox.heightF() - (meters / OSM::distance(0.0, m_center.lonF(), 1.0, m_center.lonF()))) / 2.0; + return OSM::BoundingBox(OSM::Coordinate(bbox.min.latF() + dlat, bbox.min.lonF() + dlon), + OSM::Coordinate(bbox.max.latF() - dlat, bbox.max.lonF() - dlon)); +} diff --git a/src/map/loader/boundarysearch.h b/src/map/loader/boundarysearch.h new file mode 100644 index 0000000..db4468f --- /dev/null +++ b/src/map/loader/boundarysearch.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2020 Volker Krause + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef KOSMINDOORMAP_BOUNDARYSEARCH_H +#define KOSMINDOORMAP_BOUNDARYSEARCH_H + +#include + +#include + +namespace KOSMIndoorMap { + +/** Given a coordinate, this searches for the area that should be displayed on the map, + * so that the train station or airport at that coordinate is fully displayed. + */ +class BoundarySearch +{ +public: + /** Initialize a search around @p coord. */ + void init(OSM::Coordinate coord); + /** Seach in the (incrementally updated) @p dataSet for the bounding box. */ + OSM::BoundingBox boundingBox(const OSM::DataSet &dataSet); + +private: + /** Grow @p bbox by @p meters. */ + OSM::BoundingBox growBoundingBox(const OSM::BoundingBox &bbox, double meters) const; + /** Clamp @p bbox to be at most @p meters in size. */ + OSM::BoundingBox clampBoundingBox(const OSM::BoundingBox &bbox, double meters) const; + + OSM::Coordinate m_center; + + OSM::BoundingBox m_bbox; + std::unordered_set m_relevantIds; +}; + +} + +#endif // KOSMINDOORMAP_BOUNDARYSEARCH_H diff --git a/src/map/loader/maploader.cpp b/src/map/loader/maploader.cpp index 52a7dba..369f442 100644 --- a/src/map/loader/maploader.cpp +++ b/src/map/loader/maploader.cpp @@ -1,138 +1,173 @@ /* Copyright (C) 2020 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "maploader.h" +#include "boundarysearch.h" #include #include #include #include #include +enum { + TileZoomLevel = 17 +}; + inline void initResources() // needs to be outside of a namespace { Q_INIT_RESOURCE(assets); } using namespace KOSMIndoorMap; MapLoader::MapLoader(QObject *parent) : QObject(parent) { initResources(); connect(&m_tileCache, &TileCache::tileLoaded, this, &MapLoader::downloadFinished); } MapLoader::~MapLoader() = default; void MapLoader::loadFromO5m(const QString &fileName) { QElapsedTimer loadTime; loadTime.start(); QFile f(fileName); if (!f.open(QFile::ReadOnly)) { qCritical() << f.fileName() << f.errorString(); return; } const auto data = f.map(0, f.size()); OSM::DataSet ds; OSM::O5mParser p(&ds); p.parse(data, f.size()); m_data.setDataSet(std::move(ds)); qDebug() << "o5m loading took" << loadTime.elapsed() << "ms"; Q_EMIT done(); } void MapLoader::loadForCoordinate(double lat, double lon) { - const auto center = Tile::fromCoordinate(lat, lon, 17); - // TODO expand this - m_topLeft = m_bottomRight = center; - m_topLeft.x--; - m_topLeft.y--; - m_bottomRight.x++; - m_bottomRight.y++; - - for (auto x = m_topLeft.x; x <= m_bottomRight.x; ++x) { - for (auto y = m_topLeft.y; y <= m_bottomRight.y; ++y) { - Tile tile; - tile.x = x; - tile.y = y; - tile.z = 17; - - m_tileCache.ensureCached(tile); - } + m_tileBbox = {}; + m_pendingTiles.clear(); + m_boundarySearcher.init(OSM::Coordinate(lat, lon)); + + const auto tile = Tile::fromCoordinate(lat, lon, TileZoomLevel); + m_pendingTiles.push_back(tile); + m_loadedTiles = QRect(tile.x, tile.y, 1, 1); + downloadTiles(); +} + +MapData&& MapLoader::takeData() +{ + return std::move(m_data); +} + +void MapLoader::downloadTiles() +{ + for (const auto &tile : m_pendingTiles) { + m_tileCache.ensureCached(tile); } if (m_tileCache.pendingDownloads() == 0) { loadTiles(); } else { Q_EMIT isLoadingChanged(); } } -MapData&& MapLoader::takeData() -{ - return std::move(m_data); -} - void MapLoader::downloadFinished() { if (m_tileCache.pendingDownloads() > 0) { return; } loadTiles(); } void MapLoader::loadTiles() { QElapsedTimer loadTime; loadTime.start(); - OSM::DataSet ds; - OSM::O5mParser p(&ds); - for (auto x = m_topLeft.x; x <= m_bottomRight.x; ++x) { - for (auto y = m_topLeft.y; y <= m_bottomRight.y; ++y) { - Tile tile; - tile.x = x; - tile.y = y; - tile.z = 17; - - const auto fileName = m_tileCache.cachedTile(tile); - qDebug() << fileName; - QFile f(fileName); - if (!f.open(QFile::ReadOnly)) { - qWarning() << f.fileName() << f.errorString(); - break; - } - const auto data = f.map(0, f.size()); - p.parse(data, f.size()); + OSM::O5mParser p(&m_dataSet); + for (const auto &tile : m_pendingTiles) { + const auto fileName = m_tileCache.cachedTile(tile); + qDebug() << fileName; + QFile f(fileName); + if (!f.open(QFile::ReadOnly)) { + qWarning() << f.fileName() << f.errorString(); + break; } + const auto data = f.map(0, f.size()); + p.parse(data, f.size()); + + m_tileBbox = OSM::unite(m_tileBbox, tile.boundingBox()); } - m_data.setDataSet(std::move(ds)); + m_pendingTiles.clear(); + + const auto bbox = m_boundarySearcher.boundingBox(m_dataSet); + qDebug() << "needed bbox:" << bbox << "got:" << m_tileBbox << m_loadedTiles; + + // expand left and right + if (bbox.min.longitude < m_tileBbox.min.longitude) { + m_loadedTiles.setLeft(m_loadedTiles.left() - 1); + for (int y = m_loadedTiles.top(); y <= m_loadedTiles.bottom(); ++y) { + m_pendingTiles.push_back(Tile(m_loadedTiles.left(), y, TileZoomLevel)); + } + } + if (bbox.max.longitude > m_tileBbox.max.longitude) { + m_loadedTiles.setRight(m_loadedTiles.right() + 1); + for (int y = m_loadedTiles.top(); y <= m_loadedTiles.bottom(); ++y) { + m_pendingTiles.push_back(Tile(m_loadedTiles.right(), y, TileZoomLevel)); + } + } + + // expand top/bottom: note that geographics and slippy map tile coordinates have a different understanding on what is "top" + if (bbox.max.latitude > m_tileBbox.max.latitude) { + m_loadedTiles.setTop(m_loadedTiles.top() - 1); + for (int x = m_loadedTiles.left(); x <= m_loadedTiles.right(); ++x) { + m_pendingTiles.push_back(Tile(x, m_loadedTiles.top(), TileZoomLevel)); + } + } + if (bbox.min.latitude < m_tileBbox.min.latitude) { + m_loadedTiles.setBottom(m_loadedTiles.bottom() + 1); + for (int x = m_loadedTiles.left(); x <= m_loadedTiles.right(); ++x) { + m_pendingTiles.push_back(Tile(x, m_loadedTiles.bottom(), TileZoomLevel)); + } + } + + if (!m_pendingTiles.empty()) { + downloadTiles(); + return; + } + + m_data.setDataSet(std::move(m_dataSet)); + m_data.setBoundingBox(bbox); qDebug() << "o5m loading took" << loadTime.elapsed() << "ms"; Q_EMIT isLoadingChanged(); Q_EMIT done(); } bool MapLoader::isLoading() const { return m_tileCache.pendingDownloads() > 0; } diff --git a/src/map/loader/maploader.h b/src/map/loader/maploader.h index deb3c06..5b8d39f 100644 --- a/src/map/loader/maploader.h +++ b/src/map/loader/maploader.h @@ -1,71 +1,77 @@ /* Copyright (C) 2020 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KOSMINDOORMAP_MAPLOADER_H #define KOSMINDOORMAP_MAPLOADER_H +#include "boundarysearch.h" #include "mapdata.h" #include "tilecache.h" #include #include +#include namespace KOSMIndoorMap { /** Loader for OSM data for a single station or airport. */ class MapLoader : public QObject { Q_OBJECT /** Indicates we are downloading content. Use for progress display. */ Q_PROPERTY(bool isLoading READ isLoading NOTIFY isLoadingChanged) public: explicit MapLoader(QObject *parent = nullptr); ~MapLoader(); /** Load a single O5M file. */ void loadFromO5m(const QString &fileName); /** Load map for the given coordinates. * This can involve online access. */ Q_INVOKABLE void loadForCoordinate(double lat, double lon); /** Take out the completely loaded result. * Do this before loading the next map with the same loader. */ MapData&& takeData(); bool isLoading() const; Q_SIGNALS: /** Emitted when the requested data has been loaded. */ void done(); void isLoadingChanged(); private: + void downloadTiles(); void downloadFinished(); void loadTiles(); + OSM::DataSet m_dataSet; MapData m_data; TileCache m_tileCache; - Tile m_topLeft; - Tile m_bottomRight; + OSM::BoundingBox m_tileBbox; + QRect m_loadedTiles; + std::vector m_pendingTiles; + BoundarySearch m_boundarySearcher; }; } #endif // KOSMINDOORMAP_MAPLOADER_H diff --git a/src/osm/datatypes.h b/src/osm/datatypes.h index 27fd755..9d20cca 100644 --- a/src/osm/datatypes.h +++ b/src/osm/datatypes.h @@ -1,354 +1,363 @@ /* Copyright (C) 2020 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OSM_DATATYPES_H #define OSM_DATATYPES_H #include #include #include #include #include namespace OSM { class DataSet; /** OSM element identifier. */ typedef int64_t Id; /** Coordinate, stored as 1e7 * degree to avoid floating point precision issues, * and offset to unsigned values to make the z-order curve work. * Can be in an invalid state with coordinates out of range, see isValid(). * @see https://en.wikipedia.org/wiki/Z-order_curve for the z-order curve stuff */ class Coordinate { public: Coordinate() = default; explicit constexpr Coordinate(double lat, double lon) : latitude((lat + 90.0) * 10'000'000) , longitude((lon + 180.0) * 10'000'000) {} explicit constexpr Coordinate(uint32_t lat, uint32_t lon) : latitude(lat) , longitude(lon) {} /** Create a coordinate from a z-order curve index. */ explicit constexpr Coordinate(uint64_t z) : latitude(0) , longitude(0) { for (int i = 0; i < 32; ++i) { latitude += (z & (1ull << (i * 2))) >> i; longitude += (z & (1ull << (1 + i * 2))) >> (i + 1); } } constexpr inline bool isValid() const { return latitude != std::numeric_limits::max() && longitude != std::numeric_limits::max(); } constexpr inline bool operator==(Coordinate other) const { return latitude == other.latitude && longitude == other.longitude; } /** Z-order curve value for this coordinate. */ constexpr inline uint64_t z() const { uint64_t z = 0; for (int i = 0; i < 32; ++i) { z += ((uint64_t)latitude & (1 << i)) << i; z += ((uint64_t)longitude & (1 << i)) << (i + 1); } return z; } constexpr inline double latF() const { return (latitude / 10'000'000.0) - 90.0; } constexpr inline double lonF() const { return (longitude / 10'000'000.0) - 180.0; } uint32_t latitude = std::numeric_limits::max(); uint32_t longitude = std::numeric_limits::max(); }; /** Bounding box, ie. a pair of coordinates. */ class BoundingBox { public: constexpr BoundingBox() = default; constexpr inline BoundingBox(Coordinate c1, Coordinate c2) : min(c1) , max(c2) {} constexpr inline bool isValid() const { return min.isValid() && max.isValid(); } constexpr inline bool operator==(BoundingBox other) const { return min == other.min && max == other.max; } constexpr inline uint32_t width() const { return max.longitude - min.longitude; } constexpr inline uint32_t height() const { return max.latitude - min.latitude; } + constexpr inline double widthF() const + { + return width() / 10'000'000.0; + } + constexpr inline double heightF() const + { + return height() / 10'000'000.0; + } + constexpr inline Coordinate center() const { return Coordinate(min.latitude + height() / 2, min.longitude + width() / 2); } Coordinate min; Coordinate max; }; constexpr inline BoundingBox unite(BoundingBox bbox1, BoundingBox bbox2) { if (!bbox1.isValid()) { return bbox2; } if (!bbox2.isValid()) { return bbox1; } BoundingBox ret; ret.min.latitude = std::min(bbox1.min.latitude, bbox2.min.latitude); ret.min.longitude = std::min(bbox1.min.longitude, bbox2.min.longitude); ret.max.latitude = std::max(bbox1.max.latitude, bbox2.max.latitude); ret.max.longitude = std::max(bbox1.max.longitude, bbox2.max.longitude); return ret; } constexpr inline bool intersects(BoundingBox bbox1, BoundingBox bbox2) { return !(bbox2.min.latitude > bbox1.max.latitude || bbox2.max.latitude < bbox1.min.latitude || bbox2.min.longitude > bbox1.max.longitude || bbox2.max.longitude < bbox1.min.longitude); } constexpr inline bool contains(BoundingBox bbox, Coordinate coord) { return bbox.min.latitude <= coord.latitude && bbox.max.latitude >= coord.latitude && bbox.min.longitude <= coord.longitude && bbox.max.longitude >= coord.longitude; } constexpr inline uint32_t latitudeDistance(BoundingBox bbox1, BoundingBox bbox2) { return bbox1.max.latitude < bbox2.min.latitude ? bbox2.min.latitude - bbox1.max.latitude : bbox1.min.latitude - bbox2.max.latitude; } constexpr inline uint32_t longitudeDifference(BoundingBox bbox1, BoundingBox bbox2) { return bbox1.max.longitude < bbox2.min.longitude ? bbox2.min.longitude - bbox1.max.longitude : bbox1.min.longitude - bbox2.max.longitude; } /** A key of an OSM tag. * See DataSet::tagKey(). */ class TagKey { public: constexpr inline TagKey() = default; constexpr inline const char* name() const { return key; } constexpr inline bool isNull() const { return !key; } // yes, pointer compare is enough here inline constexpr bool operator<(TagKey other) const { return key < other.key; } inline constexpr bool operator==(TagKey other) const { return key == other.key; } inline constexpr bool operator!=(TagKey other) const { return key != other.key; } private: friend class DataSet; explicit constexpr inline TagKey(const char *keyData) : key(keyData) {} const char* key = nullptr; }; /** An OSM element tag. */ class Tag { public: inline constexpr bool operator<(const Tag &other) const { return key < other.key; } TagKey key; QString value; }; /** An OSM node. */ class Node { public: constexpr inline bool operator<(const Node &other) const { return id < other.id; } QString url() const; Id id; Coordinate coordinate; std::vector tags; }; /** An OSM way. */ class Way { public: constexpr inline bool operator<(const Way &other) const { return id < other.id; } bool isClosed() const; QString url() const; Id id; mutable BoundingBox bbox; std::vector nodes; std::vector tags; }; /** Element type. */ enum class Type : uint8_t { Null, Node, Way, Relation }; /** A member in a relation. */ // TODO this has 7 byte padding, can we make this more efficient? class Member { public: Id id; QString role; Type type; }; /** An OSM relation. */ class Relation { public: constexpr inline bool operator<(const Relation &other) const { return id < other.id; } QString url() const; Id id; mutable BoundingBox bbox; std::vector members; std::vector tags; }; /** A set of nodes, ways and relations. */ class DataSet { public: explicit DataSet(); DataSet(const DataSet&) = delete; DataSet(DataSet &&other); ~DataSet(); DataSet& operator=(const DataSet&) = delete; DataSet& operator=(DataSet &&); void addNode(Node &&node); void addWay(Way &&way); void addRelation(Relation &&rel); /** Look up a tag key for the given tag name, if it exists. * If no key exists, an empty/invalid/null key is returned. * Use this for tag lookup, not for creating/adding tags. */ TagKey tagKey(const char *keyName) const; enum TagKeyMemory { TagKeyIsPersistent, TagKeyIsTransient }; /** Create a tag key for the given tag name. If none exist yet a new one is created. * Use this for creating tags, not for lookup, prefer tagKey() for that. * @param keyMemOpt specifies whether @p keyName is persisent for the lifetime of this * instance and thus can be used without requiring a copy. If the memory is transient * the string is copied if needed, and released in the DataSet destructor. */ TagKey makeTagKey(const char *keyName, TagKeyMemory keyMemOpt = TagKeyIsTransient); std::vector nodes; std::vector ways; std::vector relations; private: std::vector m_tagKeyRegistry; std::vector m_stringPool; }; /** Returns the tag value for @p key of @p elem. */ template inline QString tagValue(const Elem& elem, TagKey key) { const auto it = std::lower_bound(elem.tags.begin(), elem.tags.end(), key, [](const auto &lhs, const auto &rhs) { return lhs.key < rhs; }); if (it != elem.tags.end() && (*it).key == key) { return (*it).value; } return {}; } /** Returns the tag value for key name @p keyName of @p elem. * @warning This is slow due to doing a linear search and string comparissons. * Where possible avoid this in favor of tagValue(). */ template inline QString tagValue(const Elem& elem, const char *keyName) { const auto it = std::find_if(elem.tags.begin(), elem.tags.end(), [keyName](const auto &tag) { return std::strcmp(tag.key.name(), keyName) == 0; }); if (it != elem.tags.end()) { return (*it).value; } return {}; } /** Inserts a new tag, or replaces an existing one with the same key. */ template inline void setTag(Elem &elem, Tag &&tag) { const auto it = std::lower_bound(elem.tags.begin(), elem.tags.end(), tag); if (it == elem.tags.end() || (*it).key != tag.key) { elem.tags.insert(it, std::move(tag)); } else { (*it) = std::move(tag); } } /** Inserts a new tag, or updates an existing one. */ template inline void setTagValue(Elem &elem, TagKey key, const QString &value) { Tag tag{ key, value }; setTag(elem, std::move(tag)); } template inline bool operator<(const Elem &elem, Id id) { return elem.id < id; } } QDebug operator<<(QDebug debug, OSM::Coordinate coord); QDebug operator<<(QDebug debug, OSM::BoundingBox bbox); #endif // OSM_DATATYPES_H