1 #ifndef OSMIUM_RELATIONS_MEMBERS_DATABASE_HPP 2 #define OSMIUM_RELATIONS_MEMBERS_DATABASE_HPP 3 4 /* 5 6 This file is part of Osmium (https://osmcode.org/libosmium). 7 8 Copyright 2013-2020 Jochen Topf <jochen@topf.org> and others (see README). 9 10 Boost Software License - Version 1.0 - August 17th, 2003 11 12 Permission is hereby granted, free of charge, to any person or organization 13 obtaining a copy of the software and accompanying documentation covered by 14 this license (the "Software") to use, reproduce, display, distribute, 15 execute, and transmit the Software, and to prepare derivative works of the 16 Software, and to permit third-parties to whom the Software is furnished to 17 do so, all subject to the following: 18 19 The copyright notices in the Software and this entire statement, including 20 the above license grant, this restriction and the following disclaimer, 21 must be included in all copies of the Software, in whole or in part, and 22 all derivative works of the Software, unless such copies or derivative 23 works are solely in the form of machine-executable object code generated by 24 a source language processor. 25 26 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 29 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 30 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 31 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 32 DEALINGS IN THE SOFTWARE. 33 34 */ 35 36 #include <osmium/osm/object.hpp> 37 #include <osmium/osm/relation.hpp> 38 #include <osmium/osm/types.hpp> 39 #include <osmium/relations/relations_database.hpp> 40 #include <osmium/storage/item_stash.hpp> 41 #include <osmium/util/iterator.hpp> 42 43 #include <algorithm> 44 #include <cassert> 45 #include <cstddef> 46 #include <limits> 47 #include <tuple> 48 #include <type_traits> 49 #include <vector> 50 51 namespace osmium { 52 53 namespace relations { 54 55 /** 56 * This is the parent class for the MembersDatabase class. All the 57 * functionality which doesn't depend on the template parameter used 58 * in derived databases is contained in this class. 59 * 60 * Usually you want to use the MembersDatabase class only. 61 */ 62 class MembersDatabaseCommon { 63 64 struct element { 65 66 /** 67 * Special value used for member_num to mark the element as 68 * removed. 69 */ 70 enum { 71 removed_value = std::numeric_limits<std::size_t>::max() 72 }; 73 74 /** 75 * Object ID of this relation member. Can be a node, way, 76 * or relation ID. It depends on the database in which this 77 * object is stored which kind of object is referenced here. 78 */ 79 osmium::object_id_type member_id; 80 81 /** 82 * Position of this member in the parent relation. 83 */ 84 std::size_t member_num; 85 86 /** 87 * Position of the parent relation in the relations database. 88 */ 89 std::size_t relation_pos; 90 91 /** 92 * Handle to the stash where the object is stored. 93 * 94 * The default value is the invalid one signifying that the 95 * object hasn't been found yet. 96 */ 97 osmium::ItemStash::handle_type object_handle; 98 elementosmium::relations::MembersDatabaseCommon::element99 explicit element(std::size_t rel_pos, osmium::object_id_type memb_id, std::size_t memb_num) noexcept : 100 member_id(memb_id), 101 member_num(memb_num), 102 relation_pos(rel_pos) { 103 } 104 105 /** 106 * This constructor is used to create dummy elements that 107 * can be compared to the elements in a vector using the 108 * equal_range algorithm. 109 */ elementosmium::relations::MembersDatabaseCommon::element110 explicit element(osmium::object_id_type m_id) noexcept : 111 member_id(m_id), 112 member_num(0), 113 relation_pos(0) { 114 } 115 is_removedosmium::relations::MembersDatabaseCommon::element116 bool is_removed() const noexcept { 117 return member_num == removed_value; 118 } 119 removeosmium::relations::MembersDatabaseCommon::element120 void remove() noexcept { 121 member_num = removed_value; 122 } 123 operator <osmium::relations::MembersDatabaseCommon::element124 bool operator<(const element& other) const noexcept { 125 return std::tie(member_id, member_num, relation_pos) < 126 std::tie(other.member_id, other.member_num, other.relation_pos); 127 } 128 129 }; // struct element 130 131 // comparison function only comparing member_id. 132 struct compare_member_id { operator ()osmium::relations::MembersDatabaseCommon::compare_member_id133 bool operator()(const element& a, const element& b) const noexcept { 134 return a.member_id < b.member_id; 135 } 136 }; 137 138 std::vector<element> m_elements{}; 139 140 protected: 141 142 osmium::ItemStash& m_stash; 143 osmium::relations::RelationsDatabase& m_relations_db; 144 145 #ifndef NDEBUG 146 // This is used only in debug builds to make sure the 147 // prepare_for_lookup() function is called at the right place. 148 bool m_init_phase = true; 149 #endif 150 151 using iterator = std::vector<element>::iterator; 152 using const_iterator = std::vector<element>::const_iterator; 153 find(osmium::object_id_type id)154 iterator_range<iterator> find(osmium::object_id_type id) { 155 return make_range(std::equal_range(m_elements.begin(), m_elements.end(), element{id}, compare_member_id{})); 156 } 157 find(osmium::object_id_type id) const158 iterator_range<const_iterator> find(osmium::object_id_type id) const { 159 return make_range(std::equal_range(m_elements.cbegin(), m_elements.cend(), element{id}, compare_member_id{})); 160 } 161 count_not_removed(const iterator_range<iterator> & range)162 static typename iterator_range<iterator>::iterator::difference_type count_not_removed(const iterator_range<iterator>& range) noexcept { 163 return std::count_if(range.begin(), range.end(), [](const element& elem) { 164 return !elem.is_removed(); 165 }); 166 } 167 add_object(const osmium::OSMObject & object,iterator_range<iterator> & range)168 void add_object(const osmium::OSMObject& object, iterator_range<iterator>& range) { 169 const auto handle = m_stash.add_item(object); 170 for (auto& elem : range) { 171 elem.object_handle = handle; 172 } 173 } 174 MembersDatabaseCommon(osmium::ItemStash & stash,osmium::relations::RelationsDatabase & relations_db)175 MembersDatabaseCommon(osmium::ItemStash& stash, osmium::relations::RelationsDatabase& relations_db) : 176 m_stash(stash), 177 m_relations_db(relations_db) { 178 } 179 180 public: 181 182 /** 183 * Return an estimate of the number of bytes currently needed 184 * for the MembersDatabase. This does NOT include the memory used 185 * in the stash. Used for debugging. 186 */ used_memory() const187 std::size_t used_memory() const noexcept { 188 return sizeof(element) * m_elements.capacity() + 189 sizeof(MembersDatabaseCommon); 190 } 191 192 /** 193 * The number of members tracked in the database. Includes 194 * members tracked, but not found yet, members found and members 195 * marked as removed. 196 * 197 * Complexity: Constant. 198 */ size() const199 std::size_t size() const noexcept { 200 return m_elements.size(); 201 } 202 203 /** 204 * Result from the count() function. 205 */ 206 struct counts { 207 /// The number of members tracked and not found yet. 208 std::size_t tracked = 0; 209 /// The number of members tracked and found already. 210 std::size_t available = 0; 211 /// The number of members that were tracked, found and then removed because of a completed relation. 212 std::size_t removed = 0; 213 }; 214 215 /** 216 * Counts the number of members in different states. Usually only 217 * used for testing and debugging. 218 * 219 * Complexity: Linear in the number of members tracked. 220 */ count() const221 counts count() const noexcept { 222 counts c; 223 224 for (const auto& elem : m_elements) { 225 if (elem.is_removed()) { 226 ++c.removed; 227 } else if (elem.object_handle.valid()) { 228 ++c.available; 229 } else { 230 ++c.tracked; 231 } 232 } 233 234 return c; 235 } 236 237 /** 238 * Tell the database that you are interested in an object with 239 * the specified id and that it is a member of the given relation 240 * (as specified through the relation handle). 241 * 242 * @param rel_handle Relation this object is a member of. 243 * @param member_id Id of an object of type TObject. 244 * @param member_num This is the nth member in the relation. 245 */ track(RelationHandle & rel_handle,osmium::object_id_type member_id,std::size_t member_num)246 void track(RelationHandle& rel_handle, osmium::object_id_type member_id, std::size_t member_num) { 247 assert(m_init_phase && "Can not call MembersDatabase::track() after MembersDatabase::prepare_for_lookup()."); 248 assert(rel_handle.relation_database() == &m_relations_db); 249 m_elements.emplace_back(rel_handle.pos(), member_id, member_num); 250 rel_handle.increment_members(); 251 } 252 253 /** 254 * Prepare the database for lookup. Call this function after 255 * calling track() for all objects needed and before adding 256 * the first object with add() or querying the first object 257 * with get(). You can only call this function once. 258 */ prepare_for_lookup()259 void prepare_for_lookup() { 260 assert(m_init_phase && "Can not call MembersDatabase::prepare_for_lookup() twice."); 261 std::sort(m_elements.begin(), m_elements.end()); 262 #ifndef NDEBUG 263 m_init_phase = false; 264 #endif 265 } 266 267 /** 268 * Remove the entry with the specified member_id and relation_id 269 * from the database. If the entry doesn't exist, nothing happens. 270 */ remove(osmium::object_id_type member_id,osmium::object_id_type relation_id)271 void remove(osmium::object_id_type member_id, osmium::object_id_type relation_id) { 272 const auto range = find(member_id); 273 274 if (range.empty()) { 275 return; 276 } 277 278 // If this is the last time this object was needed, remove it 279 // from the stash. 280 if (count_not_removed(range) == 1) { 281 m_stash.remove_item(range.begin()->object_handle); 282 } 283 284 for (auto& elem : range) { 285 if (!elem.is_removed() && relation_id == m_relations_db[elem.relation_pos]->id()) { 286 elem.remove(); 287 break; 288 } 289 } 290 } 291 292 /** 293 * Find the object with the specified id in the database and 294 * return a pointer to it. Returns nullptr if there is no object 295 * with that id in the database. 296 * 297 * Complexity: Logarithmic in the number of members tracked (as 298 * returned by size()). 299 */ get_object(osmium::object_id_type id) const300 const osmium::OSMObject* get_object(osmium::object_id_type id) const { 301 assert(!m_init_phase && "Call MembersDatabase::prepare_for_lookup() before calling get_object()."); 302 const auto range = find(id); 303 if (range.empty()) { 304 return nullptr; 305 } 306 const auto handle = range.begin()->object_handle; 307 if (handle.valid()) { 308 return &m_stash.get<osmium::OSMObject>(handle); 309 } 310 return nullptr; 311 } 312 313 }; // class MembersDatabaseCommon 314 315 /** 316 * A MembersDatabase is used together with a RelationsDatabase to 317 * bring a relation and their members together. It tracks all members 318 * of a specific type needed to complete a relation. 319 * 320 * More documentation is in the MembersDatabaseCommon parent class 321 * which contains all the pieces that aren't dependent on the 322 * template parameter. 323 * 324 * @tparam TObject The object type stores in the members database. 325 * Can be osmium::Node, Way, or Relation. 326 */ 327 template <typename TObject> 328 class MembersDatabase : public MembersDatabaseCommon { 329 330 static_assert(std::is_base_of<osmium::OSMObject, TObject>::value, "TObject must be osmium::Node, Way, or Relation."); 331 332 public: 333 334 /** 335 * Construct a MembersDatabase. 336 * 337 * @param stash Reference to an ItemStash object. All member objects 338 * will be stored in this stash. It must be available 339 * until the MembersDatabase is destroyed. 340 * @param relation_db The RelationsDatabase where relations are 341 * stored. Usually it will use the same ItemStash 342 * as the MembersDatabase. 343 */ MembersDatabase(osmium::ItemStash & stash,osmium::relations::RelationsDatabase & relation_db)344 MembersDatabase(osmium::ItemStash& stash, osmium::relations::RelationsDatabase& relation_db) : 345 MembersDatabaseCommon(stash, relation_db) { 346 } 347 348 /** 349 * Add the specified object to the database. 350 * 351 * @param object Object to add. 352 * @param func If the object is the last member to complete a 353 * relation, this function is called with the relation 354 * as a parameter. 355 * @returns true if the object was actually added, false if no 356 * relation needed this object. 357 */ 358 template <typename TFunc> add(const TObject & object,TFunc && func)359 bool add(const TObject& object, TFunc&& func) { 360 assert(!m_init_phase && "Call MembersDatabase::prepare_for_lookup() before calling add()."); 361 auto range = find(object.id()); 362 363 if (range.empty()) { 364 // No relation needs this object. 365 return false; 366 } 367 368 // At least one relation needs this object. Store it and 369 // "tell" all relations. 370 add_object(object, range); 371 372 for (auto& elem : range) { 373 assert(!elem.is_removed()); 374 assert(elem.member_id == object.id()); 375 376 auto rel_handle = m_relations_db[elem.relation_pos]; 377 assert(elem.member_num < rel_handle->members().size()); 378 rel_handle.decrement_members(); 379 380 if (rel_handle.has_all_members()) { 381 func(rel_handle); 382 } 383 } 384 385 return true; 386 } 387 388 /** 389 * Find the object with the specified id in the database and 390 * return a pointer to it. Returns nullptr if there is no object 391 * with that id in the database. 392 * 393 * Complexity: Logarithmic in the number of members tracked (as 394 * returned by size()). 395 */ get(osmium::object_id_type id) const396 const TObject* get(osmium::object_id_type id) const { 397 assert(!m_init_phase && "Call MembersDatabase::prepare_for_lookup() before calling get()."); 398 return static_cast<const TObject*>(get_object(id)); 399 } 400 401 }; // class MembersDatabase 402 403 } // namespace relations 404 405 } // namespace osmium 406 407 #endif // OSMIUM_RELATIONS_MEMBERS_DATABASE_HPP 408