diff --git a/src/map/scene/scenegraph.cpp b/src/map/scene/scenegraph.cpp index dc22a7b..c5a791a 100644 --- a/src/map/scene/scenegraph.cpp +++ b/src/map/scene/scenegraph.cpp @@ -1,129 +1,132 @@ /* 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 "scenegraph.h" #include "scenegraphitem.h" #include #include #include using namespace KOSMIndoorMap; SceneGraph::SceneGraph() = default; SceneGraph::SceneGraph(SceneGraph&&) = default; SceneGraph::~SceneGraph() = default; SceneGraph& SceneGraph::operator=(SceneGraph &&other) = default; void SceneGraph::addItem(std::unique_ptr &&item) { m_items.push_back(std::move(item)); } void SceneGraph::zSort() { /* The MapCSS spec says we have to render in the following order: * - Objects with lower layer should always be rendered first. * - Within a layer, first all fills are rendered, then all casings, then all strokes, then all icons and labels. * - Within each of those categories, objects are ordered according to z-index. * - If all of the above are equal, the order is undefined. */ std::stable_sort(m_items.begin(), m_items.end(), [](const auto &lhs, const auto &rhs) { if (lhs->level == rhs->level) { if (lhs->layer == rhs->layer) { return lhs->z < rhs->z; } return lhs->layer < rhs->layer; } return lhs->level < rhs->level; }); recomputeLayerIndex(); } void SceneGraph::beginSwap() { m_items.clear(); m_layerOffsets.clear(); } void SceneGraph::endSwap() { } QColor SceneGraph::backgroundColor() const { return m_bgColor; } void SceneGraph::setBackgroundColor(const QColor &bg) { m_bgColor = bg; } void SceneGraph::recomputeLayerIndex() { m_layerOffsets.clear(); if (m_items.empty()) { return; } auto prevLayer = m_items.front()->layer; auto prevIndex = 0; for (auto it = m_items.begin(); it != m_items.end();) { it = std::upper_bound(it, m_items.end(), prevLayer, [](auto lhs, const auto &rhs) { return lhs < rhs->layer; }); const auto nextIndex = std::distance(m_items.begin(), it); m_layerOffsets.push_back(std::make_pair(prevIndex, nextIndex)); prevIndex = nextIndex; if (it != m_items.end()) { prevLayer = (*it)->layer; } } } void SceneGraph::itemsAt(QPointF pos) { // ### temporary for testing for (const auto &item : m_items) { if (item->inSceneSpace() && item->boundingRect().contains(pos)) { - qDebug() << item->element.url() << item->element.tagValue("name"); + qDebug() << item->element.url(); + for (auto it = item->element.tagsBegin(); it != item->element.tagsEnd(); ++it) { + qDebug() << " " << (*it).key << (*it).value; + } } // TODO HUD space elements } } const std::vector& SceneGraph::layerOffsets() const { return m_layerOffsets; } SceneGraph::SceneGraphItemIter SceneGraph::itemsBegin(SceneGraph::LayerOffset layer) const { return m_items.begin() + layer.first; } SceneGraph::SceneGraphItemIter SceneGraph::itemsEnd(SceneGraph::LayerOffset layer) const { return m_items.begin() + layer.second; } std::size_t SceneGraph::itemCount() const { return m_items.size(); } diff --git a/src/osm/element.cpp b/src/osm/element.cpp index d2c827d..0608176 100644 --- a/src/osm/element.cpp +++ b/src/osm/element.cpp @@ -1,207 +1,237 @@ /* 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 "element.h" using namespace OSM; Coordinate Element::center() const { switch (type()) { case Type::Null: return {}; case Type::Node: return node()->coordinate; case Type::Way: return way()->bbox.center(); case Type::Relation: return relation()->bbox.center(); } return {}; } BoundingBox Element::boundingBox() const { switch (type()) { case Type::Null: return {}; case Type::Node: return BoundingBox(node()->coordinate, node()->coordinate); case Type::Way: return way()->bbox; case Type::Relation: return relation()->bbox; } return {}; } QString Element::tagValue(const QLatin1String &key) const { switch (type()) { case Type::Null: return {}; case Type::Node: return OSM::tagValue(*node(), key); case Type::Way: return OSM::tagValue(*way(), key); case Type::Relation: return OSM::tagValue(*relation(), key); } return {}; } QString OSM::Element::tagValue(const char *key) const { return tagValue(QLatin1String(key)); } +std::vector::const_iterator OSM::Element::tagsBegin() const +{ + switch (type()) { + case Type::Null: + Q_UNREACHABLE(); + case Type::Node: + return node()->tags.begin(); + case Type::Way: + return way()->tags.begin(); + case Type::Relation: + return relation()->tags.begin(); + } + Q_UNREACHABLE(); +} + +std::vector::const_iterator OSM::Element::tagsEnd() const +{ + switch (type()) { + case Type::Null: + Q_UNREACHABLE(); + case Type::Node: + return node()->tags.end(); + case Type::Way: + return way()->tags.end(); + case Type::Relation: + return relation()->tags.end(); + } + Q_UNREACHABLE(); +} + QString Element::url() const { switch (type()) { case Type::Null: return {}; case Type::Node: return node()->url(); case Type::Way: return way()->url(); case Type::Relation: return relation()->url(); } return {}; } template static void appendNodesFromWay(const DataSet &dataSet, std::vector &nodes, const Iter& nodeBegin, const Iter &nodeEnd) { nodes.reserve(nodes.size() + std::distance(nodeBegin, nodeEnd)); for (auto it = nodeBegin; it != nodeEnd; ++it) { const auto nodeIt = std::lower_bound(dataSet.nodes.begin(), dataSet.nodes.end(), (*it)); if (nodeIt == dataSet.nodes.end() || (*nodeIt).id != (*it)) { continue; } nodes.push_back(&(*nodeIt)); } } static OSM::Id appendNextPath(const DataSet &dataSet, std::vector &nodes, OSM::Id startNode, std::vector &ways) { if (ways.empty()) { return {}; } for (auto it = std::next(ways.begin()); it != ways.end(); ++it) { assert(!(*it)->nodes.empty()); // ensured in the caller if ((*it)->nodes.front() == startNode) { appendNodesFromWay(dataSet, nodes, (*it)->nodes.begin(), (*it)->nodes.end()); const auto lastNodeId = (*it)->nodes.back(); ways.erase(it); return lastNodeId; } // path segments can also be backwards if ((*it)->nodes.back() == startNode) { appendNodesFromWay(dataSet, nodes, (*it)->nodes.rbegin(), (*it)->nodes.rend()); const auto lastNodeId = (*it)->nodes.front(); ways.erase(it); return lastNodeId; } } return {}; } std::vector Element::outerPath(const DataSet &dataSet) const { switch (type()) { case Type::Null: return {}; case Type::Node: return {node()}; case Type::Way: { std::vector nodes; appendNodesFromWay(dataSet, nodes, way()->nodes.begin(), way()->nodes.end()); return nodes; } case Type::Relation: { if (tagValue("type") != QLatin1String("multipolygon")) { return {}; } // collect the relevant ways std::vector ways; for (const auto &member : relation()->members) { if (member.role != QLatin1String("outer")) { continue; } const auto it = std::lower_bound(dataSet.ways.begin(), dataSet.ways.end(), member.id); if (it != dataSet.ways.end() && (*it).id == member.id && !(*it).nodes.empty()) { ways.push_back(&(*it)); } } // stitch them together (there is no well-defined order) std::vector nodes; for (auto it = ways.begin(); it != ways.end();) { assert(!(*it)->nodes.empty()); // ensured above appendNodesFromWay(dataSet, nodes, (*it)->nodes.begin(), (*it)->nodes.end()); const auto startNode = (*it)->nodes.front(); auto lastNode = (*it)->nodes.back(); do { lastNode = appendNextPath(dataSet, nodes, lastNode, ways); } while (lastNode && lastNode != startNode); it = ways.erase(it); } return nodes; } } return {}; } void Element::recomputeBoundingBox(const DataSet &dataSet) { switch (type()) { case Type::Null: case Type::Node: break; case Type::Way: way()->bbox = std::accumulate(way()->nodes.begin(), way()->nodes.end(), OSM::BoundingBox(), [&dataSet](auto bbox, auto nodeId) { const auto nodeIt = std::lower_bound(dataSet.nodes.begin(), dataSet.nodes.end(), nodeId); if (nodeIt == dataSet.nodes.end() || (*nodeIt).id != nodeId) { return bbox; } return OSM::unite(bbox, {(*nodeIt).coordinate, (*nodeIt).coordinate}); }); break; case Type::Relation: relation()->bbox = {}; for_each_member(dataSet, *relation(), [this, &dataSet](auto mem) { mem.recomputeBoundingBox(dataSet); relation()->bbox = OSM::unite(relation()->bbox, mem.boundingBox()); }); break; } } diff --git a/src/osm/element.h b/src/osm/element.h index d4a3197..00539f2 100644 --- a/src/osm/element.h +++ b/src/osm/element.h @@ -1,147 +1,149 @@ /* 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_ELEMENT_H #define OSM_ELEMENT_H #include "datatypes.h" #include namespace OSM { namespace Internal { template class TaggedPointer { public: explicit inline constexpr TaggedPointer(T *ptr, uint8_t tag) : m_data(reinterpret_cast(ptr) | (tag & TagMask)) {} inline T* get() const { return reinterpret_cast(m_data & ~TagMask); } inline uint8_t tag() const { return m_data & TagMask; } inline operator bool() const { return (m_data & ~TagMask); } private: enum { TagMask = 0x3 }; std::uintptr_t m_data; }; } /** A reference to any of OSM::Node/OSM::Way/OSM::Relation. * Lifetime of the referenced object needs to extend beyond the lifetime of this. */ class Element { public: inline constexpr Element() : m_elem(nullptr, static_cast(Type::Null)) {} inline Element(const Node *node) : m_elem(node, static_cast(Type::Node)) {} inline Element(const Way *way) : m_elem(way, static_cast(Type::Way)) {} inline Element(const Relation *relation) : m_elem(relation, static_cast(Type::Relation)) {} inline Type type() const { return static_cast(m_elem.tag()); } inline const Node* node() const { return static_cast(m_elem.get()); } inline const Way* way() const { return static_cast(m_elem.get()); } inline const Relation* relation() const { return static_cast(m_elem.get()); } Coordinate center() const; BoundingBox boundingBox() const; QString tagValue(const QLatin1String &key) const; QString tagValue(const char *key) const; + std::vector::const_iterator tagsBegin() const; + std::vector::const_iterator tagsEnd() const; QString url() const; /** Returns all nodes belonging to the outer path of this element. * In the simplest case that's a single closed polygon, but it can also be a sequence of multiple * closed loop polygons, or a polyline. */ std::vector outerPath(const DataSet &dataSet) const; /** Recompute the bounding box of this element. * We usually assume those to be provided by Overpass/osmconvert, but there seem to be cases where those * aren't reliable. */ void recomputeBoundingBox(const DataSet &dataSet); private: Internal::TaggedPointer m_elem; }; enum ForeachFlag : uint8_t { IncludeRelations = 1, IncludeWays = 2, IncludeNodes = 4, IterateAll = IncludeRelations | IncludeWays | IncludeNodes, }; template inline void for_each(const DataSet &dataSet, Func func, uint8_t flags = IterateAll) { if (flags & IncludeRelations) { for (const auto &rel : dataSet.relations) { func(Element(&rel)); } } if (flags & IncludeWays) { for (const auto &way : dataSet.ways) { func(Element(&way)); } } if (flags & IncludeNodes) { for (const auto &node : dataSet.nodes) { func(Element(&node)); } } } template inline void for_each_member(const DataSet &dataSet, const Relation &rel, Func func) { for (const auto &mem : rel.members) { switch (mem.type) { case Type::Null: break; case Type::Node: { const auto it = std::lower_bound(dataSet.nodes.begin(), dataSet.nodes.end(), mem.id); if (it != dataSet.nodes.end() && (*it).id == mem.id) { func(Element(&(*it))); } break; } case Type::Way: { const auto it = std::lower_bound(dataSet.ways.begin(), dataSet.ways.end(), mem.id); if (it != dataSet.ways.end() && (*it).id == mem.id) { func(Element(&(*it))); } break; } case Type::Relation: { const auto it = std::lower_bound(dataSet.relations.begin(), dataSet.relations.end(), mem.id); if (it != dataSet.relations.end() && (*it).id == mem.id) { func(Element(&(*it))); } break; } } } } } #endif // OSM_ELEMENT_H