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