From 1103562383aed3f7408965c0b19abf5e7f011b28 Mon Sep 17 00:00:00 2001 From: Constantin Shalnev Date: Fri, 4 Dec 2015 16:27:13 +0300 Subject: [PATCH] Added implementation of GpsTrackCollection --- map/gps_track_collection.cpp | 216 +++++++++++++++++++++++++++++++++++ map/gps_track_collection.hpp | 103 +++++++++++++++++ map/map.pro | 2 + 3 files changed, 321 insertions(+) create mode 100644 map/gps_track_collection.cpp create mode 100644 map/gps_track_collection.hpp diff --git a/map/gps_track_collection.cpp b/map/gps_track_collection.cpp new file mode 100644 index 0000000000..374245da67 --- /dev/null +++ b/map/gps_track_collection.cpp @@ -0,0 +1,216 @@ +#include "map/gps_track_collection.hpp" + +#include "base/assert.hpp" + +#include "std/algorithm.hpp" + +namespace +{ + +size_t const kSecondsPerHour = 60 * 60; +size_t const kLinearSearchCount = 10; + +// Simple rollbacker which restores deque state +template +class Rollbacker +{ +public: + Rollbacker(deque & cont) + : m_cont(&cont) , m_size(cont.size()) + {} + ~Rollbacker() + { + if (m_cont && m_cont->size() > m_size) + m_cont->erase(m_cont->begin() + m_size, m_cont->end()); + } + void Reset() { m_cont = nullptr; } +private: + deque * m_cont; + size_t const m_size; +}; + +} // namespace + +GpsTrackCollection::GpsTrackCollection(size_t maxSize, hours duration) + : m_maxSize(maxSize) + , m_duration(duration) + , m_lastId(0) +{ +} + +size_t GpsTrackCollection::Add(TItem const & item, pair & poppedIds) +{ + if (!m_items.empty() && m_items.back().m_timestamp > item.m_timestamp) + { + // Invalid timestamp order + poppedIds = make_pair(kInvalidId, kInvalidId); // Nothing was popped + return kInvalidId; // Nothing was added + } + + m_items.emplace_back(item); + ++m_lastId; + + poppedIds = RemoveExtraItems(); + + return m_lastId - 1; +} + +pair GpsTrackCollection::Add(vector const & items, pair & poppedIds) +{ + size_t startId = m_lastId; + size_t added = 0; + + // Rollbacker ensure strong guarantee if exception happens while adding items + Rollbacker rollbacker(m_items); + + for (auto const & item : items) + { + if (!m_items.empty() && m_items.back().m_timestamp > item.m_timestamp) + continue; + + m_items.emplace_back(item); + ++added; + } + + rollbacker.Reset(); + + if (0 == added) + { + // Invalid timestamp order + poppedIds = make_pair(kInvalidId, kInvalidId); // Nothing was popped + return make_pair(kInvalidId, kInvalidId); // Nothing was added + } + + m_lastId += added; + + poppedIds = RemoveExtraItems(); + + return make_pair(startId, startId + added - 1); +} + +hours GpsTrackCollection::GetDuration() const +{ + return m_duration; +} + +pair GpsTrackCollection::SetDuration(hours duration) +{ + m_duration = duration; + + if (m_items.empty()) + return make_pair(kInvalidId, kInvalidId); + + return RemoveExtraItems(); +} + +pair GpsTrackCollection::Clear(bool resetIds) +{ + if (m_items.empty()) + { + if (resetIds) + m_lastId = 0; + + return make_pair(kInvalidId, kInvalidId); + } + + ASSERT(m_lastId >= m_items.size(), ()); + + // Range of popped items + auto const res = make_pair(m_lastId - m_items.size(), m_lastId - 1); + + // Use move from an empty deque to free memory. + m_items = deque(); + + if (resetIds) + m_lastId = 0; + + return res; +} + +size_t GpsTrackCollection::GetSize() const +{ + return m_items.size(); +} + +size_t GpsTrackCollection::GetMaxSize() const +{ + return m_maxSize; +} + +bool GpsTrackCollection::IsEmpty() const +{ + return m_items.empty(); +} + +pair GpsTrackCollection::GetTimestampRange() const +{ + if (m_items.empty()) + return make_pair(0, 0); + + ASSERT(m_items.front().m_timestamp <= m_items.back().m_timestamp, ()); + + return make_pair(m_items.front().m_timestamp, m_items.back().m_timestamp); +} + +pair GpsTrackCollection::RemoveUntil(deque::iterator i) +{ + auto const res = make_pair(m_lastId - m_items.size(), + m_lastId - m_items.size() + distance(m_items.begin(), i) - 1); + m_items.erase(m_items.begin(), i); + return res; +} + +pair GpsTrackCollection::RemoveExtraItems() +{ + if (m_items.empty()) + return make_pair(kInvalidId, kInvalidId); // Nothing to remove + + double const lowerBound = m_items.back().m_timestamp - m_duration.count() * kSecondsPerHour; + + ASSERT(m_lastId >= m_items.size(), ()); + + if (m_items.front().m_timestamp >= lowerBound) + { + // All items lie on right side of lower bound, + // but need to remove items by size. + if (m_items.size() <= m_maxSize) + return make_pair(kInvalidId, kInvalidId); // Nothing to remove, all points survived. + return RemoveUntil(m_items.begin() + m_items.size() - m_maxSize); + } + + if (m_items.back().m_timestamp <= lowerBound) + { + // All items lie on left side of lower bound. Remove all items. + return RemoveUntil(m_items.end()); + } + + bool found = false; + auto i = m_items.begin(); + + // First, try linear search for short distance. It is common case for sliding window + // when new items will pop up old items. + for (size_t j = 0; j < kLinearSearchCount && !found; ++i, ++j) + { + ASSERT(i != m_items.end(), ()); + + if (i->m_timestamp >= lowerBound) + found = true; + } + + // If item wasn't found by linear search, since m_items are sorted by timestamp, use lower_bound to find bound + if (!found) + { + TItem t; + t.m_timestamp = lowerBound; + i = lower_bound(i, m_items.end(), t, [](TItem const & a, TItem const & b)->bool{ return a.m_timestamp < b.m_timestamp; }); + + ASSERT(i != m_items.end(), ()); + } + + // If remaining part has size more than max size then cut off to leave max size + size_t const remains = distance(i, m_items.end()); + if (remains > m_maxSize) + i += remains - m_maxSize; + + return RemoveUntil(i); +} diff --git a/map/gps_track_collection.hpp b/map/gps_track_collection.hpp new file mode 100644 index 0000000000..093002051b --- /dev/null +++ b/map/gps_track_collection.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include "platform/location.hpp" + +#include "std/chrono.hpp" +#include "std/deque.hpp" +#include "std/limits.hpp" +#include "std/utility.hpp" +#include "std/vector.hpp" + +class GpsTrackCollection final +{ +public: + static size_t const kInvalidId = numeric_limits::max(); + + using TItem = location::GpsTrackInfo; + + /// Constructor + /// @param maxSize - max number of items in collection + /// @param duration - duration in hours + GpsTrackCollection(size_t maxSize, hours duration); + + /// Adds new point in the collection. + /// @param item - item to be added. + /// @param poppedIds - output, which contains range of identifiers popped items or + /// pair(kInvalidId,kInvalidId) if nothing was removed + /// @returns the item unique identifier or kInvalidId if point has incorrect time. + size_t Add(TItem const & items, pair & poppedIds); + + /// Adds set of new points in the collection. + /// @param items - set of items to be added. + /// @param poppedIds - output, which contains range of identifiers popped items or + /// pair(kInvalidId,kInvalidId) if nothing was removed + /// @returns range of identifiers of added items or pair(kInvalidId,kInvalidId) if nothing was added + /// @note items which does not conform to timestamp sequence, is not added. + pair Add(vector const & item, pair & poppedIds); + + /// Get current duration in hours + /// @returns current duration in hours + hours GetDuration() const; + + /// Sets duration in hours. + /// @param duration - new duration value + /// @return range of item identifiers, which were removed or + /// pair(kInvalidId,kInvalidId) if nothing was removed + pair SetDuration(hours duration); + + /// Removes all points from the collection. + /// @param resetIds - if it is set to true, then new identifiers will start from 0, + /// otherwise new identifiers will continue from returned value res.second + 1 + /// @return range of item identifiers, which were removed or + /// pair(kInvalidId,kInvalidId) if nothing was removed + pair Clear(bool resetIds = true); + + /// Returns true if collection is empty, otherwise returns false. + bool IsEmpty() const; + + /// Returns number of items in the collection + size_t GetSize() const; + + /// Returns range of timestamps of collection, where res.first is lower bound and + /// res.second is upper bound. If collection is empty, then returns pair(0, 0). + pair GetTimestampRange() const; + + /// Returns max size of collection + size_t GetMaxSize() const; + + /// Enumerates items in the collection. + /// @param f - callable object, which is called with params - item and item id, + /// if f returns false, then enumeration is stopped. + /// @param pos - position index to start enumeration + template + void ForEach(F && f, size_t pos = 0) const + { + if (pos >= m_items.size()) + return; + auto i = m_items.cbegin() + pos, iend = m_items.cend(); + size_t id = m_lastId - m_items.size() + pos; + for (; i != iend; ++i, ++id) + { + TItem const & item = *i; + size_t const itemId = id; + if (!f(item, itemId)) + break; + } + } + +private: + // Removes items in range [m_items.begin(), i) and returnd + // range of identifiers of removed items + pair RemoveUntil(deque::iterator i); + + // Removes items extra by timestamp and max size + pair RemoveExtraItems(); + + size_t const m_maxSize; + + hours m_duration; + + deque m_items; // asc. sorted by timestamp + + size_t m_lastId; +}; diff --git a/map/map.pro b/map/map.pro index 40c73f83bc..bfb4b86fa6 100644 --- a/map/map.pro +++ b/map/map.pro @@ -21,6 +21,7 @@ HEADERS += \ framework.hpp \ ge0_parser.hpp \ geourl_process.hpp \ + gps_track_collection.hpp \ gps_track_container.hpp \ mwm_url.hpp \ storage_bridge.hpp \ @@ -41,6 +42,7 @@ SOURCES += \ framework.cpp \ ge0_parser.cpp \ geourl_process.cpp \ + gps_track_collection.cpp \ gps_track_container.cpp \ mwm_url.cpp \ storage_bridge.cpp \