[map] move the track stats calc logic from ElevationInfo to the TrackStatistics

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
This commit is contained in:
Kiryl Kaveryn 2025-02-17 16:35:00 +04:00 committed by Viktor Havaka
parent bc6a9e4804
commit 1783c90714
23 changed files with 484 additions and 230 deletions

View file

@ -1,14 +1,10 @@
#import "TrackInfo.h"
#include <CoreApi/Framework.h>
#include "map/gps_track_collection.hpp"
#include "map/elevation_info.hpp"
#include "map/track_statistics.hpp"
@interface TrackInfo (Core)
- (instancetype)initWithGpsTrackInfo:(GpsTrackInfo const &)info;
- (instancetype)initWithDistance:(double)distance duration:(double)duration;
- (void)setElevationInfo:(ElevationInfo const &)elevationInfo;
- (instancetype)initWithTrackStatistics:(TrackStatistics const &)statistics;
@end

View file

@ -3,8 +3,6 @@
#import "DistanceFormatter.h"
#import "DurationFormatter.h"
#include "map/elevation_info.hpp"
@implementation TrackInfo
- (BOOL)hasElevationInfo {
@ -19,31 +17,16 @@
@implementation TrackInfo (Core)
- (instancetype)initWithGpsTrackInfo:(GpsTrackInfo const &)trackInfo {
- (instancetype)initWithTrackStatistics:(TrackStatistics const &)statistics {
if (self = [super init]) {
_distance = trackInfo.m_length;
_duration = trackInfo.m_duration;
_ascent = trackInfo.m_ascent;
_descent = trackInfo.m_descent;
_maxElevation = trackInfo.m_maxElevation;
_minElevation = trackInfo.m_minElevation;
_distance = statistics.m_length;
_duration = statistics.m_duration;
_ascent = statistics.m_ascent;
_descent = statistics.m_descent;
_maxElevation = statistics.m_maxElevation;
_minElevation = statistics.m_minElevation;
}
return self;
}
- (instancetype)initWithDistance:(double)distance duration:(double)duration {
if (self = [super init]) {
_distance = distance;
_duration = duration;
}
return self;
}
- (void)setElevationInfo:(ElevationInfo const &)elevationInfo {
_ascent = elevationInfo.GetAscent();
_descent = elevationInfo.GetDescent();
_maxElevation = elevationInfo.GetMaxAltitude();
_minElevation = elevationInfo.GetMinAltitude();
}
@end

View file

@ -224,8 +224,8 @@ static Framework::ProductsPopupCloseReason ConvertProductPopupCloseReasonToCore(
GetFramework().SetTrackRecordingUpdateHandler(nullptr);
return;
}
GetFramework().SetTrackRecordingUpdateHandler([trackRecordingDidUpdate](GpsTrackInfo const & gpsTrackInfo) {
TrackInfo * info = [[TrackInfo alloc] initWithGpsTrackInfo:gpsTrackInfo];
GetFramework().SetTrackRecordingUpdateHandler([trackRecordingDidUpdate](TrackStatistics const & statistics) {
TrackInfo * info = [[TrackInfo alloc] initWithTrackStatistics:statistics];
trackRecordingDidUpdate(info);
});
}

View file

@ -12,11 +12,9 @@
self = [super init];
if (self) {
_trackId = track.GetData().m_id;
_trackInfo = [[TrackInfo alloc] initWithDistance:track.GetLengthMeters()
duration:track.GetDurationInSeconds()];
_trackInfo = [[TrackInfo alloc] initWithTrackStatistics:track.GetStatistics()];
auto const & elevationInfo = track.GetElevationInfo();
if (track.HasAltitudes() && elevationInfo.has_value()) {
[_trackInfo setElevationInfo:elevationInfo.value()];
auto const & bm = GetFramework().GetBookmarkManager();
_elevationProfileData = [[ElevationProfileData alloc] initWithTrackId:_trackId
elevationInfo:elevationInfo.value()

View file

@ -57,6 +57,8 @@ set(SRC
search_product_info.hpp
track.cpp
track.hpp
track_statistics.cpp
track_statistics.hpp
track_mark.cpp
track_mark.hpp
traffic_manager.cpp

View file

@ -4,54 +4,60 @@
#include "geometry/mercator.hpp"
ElevationInfo::ElevationInfo(kml::MultiGeometry const & geometry)
{
double distance = 0;
// Concatenate all segments.
for (size_t lineIndex = 0; lineIndex < geometry.m_lines.size(); ++lineIndex)
{
auto const & line = geometry.m_lines[lineIndex];
if (line.empty())
{
LOG(LWARNING, ("Empty line in elevation info"));
continue;
}
using namespace geometry;
using namespace mercator;
if (lineIndex == 0)
{
m_minAltitude = line.front().GetAltitude();
m_maxAltitude = m_minAltitude;
}
ElevationInfo::ElevationInfo(std::vector<GeometryLine> const & lines)
{
// Concatenate all segments.
for (size_t lineIndex = 0; lineIndex < lines.size(); ++lineIndex)
{
auto const & line = lines[lineIndex];
if (line.empty())
continue;
if (lineIndex > 0)
m_segmentsDistances.emplace_back(distance);
m_segmentsDistances.emplace_back(m_points.back().m_distance);
for (size_t pointIndex = 0; pointIndex < line.size(); ++pointIndex)
{
auto const & currentPoint = line[pointIndex];
auto const & currentPointAltitude = currentPoint.GetAltitude();
if (currentPointAltitude < m_minAltitude)
m_minAltitude = currentPointAltitude;
if (currentPointAltitude > m_maxAltitude)
m_maxAltitude = currentPointAltitude;
if (pointIndex == 0)
{
m_points.emplace_back(currentPoint, distance);
continue;
}
auto const & previousPoint = line[pointIndex - 1];
distance += mercator::DistanceOnEarth(previousPoint.GetPoint(), currentPoint.GetPoint());
m_points.emplace_back(currentPoint, distance);
auto const deltaAltitude = currentPointAltitude - previousPoint.GetAltitude();
if (deltaAltitude > 0)
m_ascent += deltaAltitude;
else
m_descent -= deltaAltitude;
}
AddPoints(line, true /* new segment */);
}
/// @todo(KK) Implement difficulty calculation.
m_difficulty = Difficulty::Unknown;
}
void ElevationInfo::AddGpsPoints(GpsPoints const & points)
{
GeometryLine line;
line.reserve(points.size());
for (auto const & point : points)
line.emplace_back(FromLatLon(point.m_latitude, point.m_longitude), point.m_altitude);
AddPoints(line);
}
void ElevationInfo::AddPoints(GeometryLine const & line, bool isNewSegment)
{
if (line.empty())
return;
double distance = m_points.empty() ? 0 : m_points.back().m_distance;
for (size_t pointIndex = 0; pointIndex < line.size(); ++pointIndex)
{
auto const & point = line[pointIndex];
if (m_points.empty())
{
m_points.emplace_back(point, distance);
continue;
}
if (isNewSegment && pointIndex == 0)
{
m_points.emplace_back(point, distance);
continue;
}
auto const & previousPoint = m_points.back().m_point;
distance += mercator::DistanceOnEarth(previousPoint.GetPoint(), point.GetPoint());
m_points.emplace_back(point, distance);
}
}

View file

@ -5,6 +5,8 @@
#include "geometry/point_with_altitude.hpp"
#include "geometry/latlon.hpp"
#include "platform/location.hpp"
#include <cstdint>
#include <string>
#include <vector>
@ -22,6 +24,8 @@ public:
};
using Points = std::vector<Point>;
using GpsPoints = std::vector<location::GpsInfo>;
using GeometryLine = kml::MultiGeometry::LineT;
using SegmentsDistances = std::vector<double>;
enum Difficulty : uint8_t
@ -33,31 +37,23 @@ public:
};
ElevationInfo() = default;
explicit ElevationInfo(kml::MultiGeometry const & geometry);
explicit ElevationInfo(std::vector<GeometryLine> const & lines);
void AddGpsPoints(GpsPoints const & points);
size_t GetSize() const { return m_points.size(); };
Points const & GetPoints() const { return m_points; };
uint32_t GetAscent() const { return m_ascent; }
uint32_t GetDescent() const { return m_descent; }
geometry::Altitude GetMinAltitude() const { return m_minAltitude; }
geometry::Altitude GetMaxAltitude() const { return m_maxAltitude; }
uint8_t GetDifficulty() const { return m_difficulty; }
SegmentsDistances const & GetSegmentsDistances() const { return m_segmentsDistances; };
private:
// Points with distance from start of the track and altitude.
Points m_points;
// Ascent in meters.
uint32_t m_ascent = 0;
// Descent in meters.
uint32_t m_descent = 0;
// Altitude in meters.
geometry::Altitude m_minAltitude = 0;
// Altitude in meters.
geometry::Altitude m_maxAltitude = 0;
// Some digital difficulty level with value in range [0-kMaxDifficulty]
// or kInvalidDifficulty when difficulty is not found or incorrect.
Difficulty m_difficulty = Difficulty::Unknown;
// Distances to the start of each segment.
SegmentsDistances m_segmentsDistances;
void AddPoints(GeometryLine const & line, bool isNewSegment = false);
};

View file

@ -1736,7 +1736,7 @@ void Framework::SetTrackRecordingUpdateHandler(TrackRecordingUpdateHandler && tr
{
m_trackRecordingUpdateHandler = std::move(trackRecordingDidUpdate);
if (m_trackRecordingUpdateHandler)
m_trackRecordingUpdateHandler(GpsTracker::Instance().GetTrackInfo());
m_trackRecordingUpdateHandler(GpsTracker::Instance().GetTrackStatistics());
}
void Framework::StopTrackRecording()
@ -1768,7 +1768,7 @@ bool Framework::IsTrackRecordingEnabled() const
void Framework::OnUpdateGpsTrackPointsCallback(vector<pair<size_t, location::GpsInfo>> && toAdd,
pair<size_t, size_t> const & toRemove,
GpsTrackInfo const & trackInfo)
TrackStatistics const & trackStatistics)
{
ASSERT(m_drapeEngine.get() != nullptr, ());
@ -1797,7 +1797,7 @@ void Framework::OnUpdateGpsTrackPointsCallback(vector<pair<size_t, location::Gps
m_drapeEngine->UpdateGpsTrackPoints(std::move(pointsAdd), std::move(indicesRemove));
if (m_trackRecordingUpdateHandler)
m_trackRecordingUpdateHandler(trackInfo);
m_trackRecordingUpdateHandler(trackStatistics);
}
void Framework::MarkMapStyle(MapStyle mapStyle)

View file

@ -15,9 +15,9 @@
#include "map/search_api.hpp"
#include "map/search_mark.hpp"
#include "map/track.hpp"
#include "map/track_statistics.hpp"
#include "map/traffic_manager.hpp"
#include "map/transit/transit_reader.hpp"
#include "map/gps_track_collection.hpp"
#include "drape_frontend/gui/skin.hpp"
#include "drape_frontend/drape_api.hpp"
@ -437,7 +437,7 @@ public:
void ConnectToGpsTracker();
void DisconnectFromGpsTracker();
using TrackRecordingUpdateHandler = platform::SafeCallback<void(GpsTrackInfo const & trackInfo)>;
using TrackRecordingUpdateHandler = platform::SafeCallback<void(TrackStatistics const & trackStatistics)>;
void StartTrackRecording();
void SetTrackRecordingUpdateHandler(TrackRecordingUpdateHandler && trackRecordingDidUpdate);
void StopTrackRecording();
@ -468,7 +468,7 @@ private:
void OnUpdateGpsTrackPointsCallback(std::vector<std::pair<size_t, location::GpsInfo>> && toAdd,
std::pair<size_t, size_t> const & toRemove,
GpsTrackInfo const & trackInfo);
TrackStatistics const & trackStatistics);
TrackRecordingUpdateHandler m_trackRecordingUpdateHandler;

View file

@ -79,9 +79,9 @@ void GpsTrack::AddPoints(vector<location::GpsInfo> const & points)
ScheduleTask();
}
GpsTrackInfo GpsTrack::GetTrackInfo() const
TrackStatistics GpsTrack::GetTrackStatistics() const
{
return m_collection ? m_collection->GetTrackInfo() : GpsTrackInfo();
return m_collection ? m_collection->GetTrackStatistics() : TrackStatistics();
}
void GpsTrack::Clear()
@ -303,7 +303,7 @@ void GpsTrack::NotifyCallback(pair<size_t, size_t> const & addedIds, pair<size_t
if (toAdd.empty())
return; // nothing to send
m_callback(std::move(toAdd), make_pair(kInvalidId, kInvalidId), m_collection->GetTrackInfo());
m_callback(std::move(toAdd), make_pair(kInvalidId, kInvalidId), m_collection->GetTrackStatistics());
}
else
{
@ -324,6 +324,6 @@ void GpsTrack::NotifyCallback(pair<size_t, size_t> const & addedIds, pair<size_t
if (toAdd.empty() && evictedIds.first == kInvalidId)
return; // nothing to send
m_callback(std::move(toAdd), evictedIds, m_collection->GetTrackInfo());
m_callback(std::move(toAdd), evictedIds, m_collection->GetTrackStatistics());
}
}

View file

@ -31,7 +31,7 @@ public:
void AddPoints(std::vector<location::GpsInfo> const & points);
/// Returns track statistics
GpsTrackInfo GetTrackInfo() const;
TrackStatistics GetTrackStatistics() const;
/// Clears any previous tracking info
/// @note Callback is called with 'toRemove' points, if some points were removed.
@ -47,7 +47,7 @@ public:
using TGpsTrackDiffCallback =
std::function<void(std::vector<std::pair<size_t, location::GpsInfo>> && toAdd,
std::pair<size_t, size_t> const & toRemove,
GpsTrackInfo const & trackInfo)>;
TrackStatistics const & trackStatistics)>;
/// Sets callback on change of gps track.
/// @param callback - callback callable object

View file

@ -2,8 +2,6 @@
#include "base/assert.hpp"
#include "geometry/distance_on_sphere.hpp"
#include <algorithm>
namespace
@ -35,7 +33,7 @@ size_t const GpsTrackCollection::kInvalidId = std::numeric_limits<size_t>::max()
GpsTrackCollection::GpsTrackCollection()
: m_lastId(0)
, m_trackInfo(GpsTrackInfo())
, m_statistics(TrackStatistics())
{}
std::pair<size_t, size_t> GpsTrackCollection::Add(std::vector<TItem> const & items)
@ -51,26 +49,7 @@ std::pair<size_t, size_t> GpsTrackCollection::Add(std::vector<TItem> const & ite
if (!m_items.empty() && m_items.back().m_timestamp > item.m_timestamp)
continue;
if (m_items.empty())
{
m_trackInfo.m_maxElevation = item.m_altitude;
m_trackInfo.m_minElevation = item.m_altitude;
}
else
{
auto const & lastItem = m_items.back();
m_trackInfo.m_length += ms::DistanceOnEarth(lastItem.GetLatLon(), item.GetLatLon());
m_trackInfo.m_duration = item.m_timestamp - m_items.front().m_timestamp;
auto const deltaAltitude = item.m_altitude - lastItem.m_altitude;
if (item.m_altitude > lastItem.m_altitude)
m_trackInfo.m_ascent += deltaAltitude;
if (item.m_altitude < lastItem.m_altitude)
m_trackInfo.m_descent -= deltaAltitude;
m_trackInfo.m_maxElevation = std::max(static_cast<double>(m_trackInfo.m_maxElevation), item.m_altitude);
m_trackInfo.m_minElevation = std::min(static_cast<double>(m_trackInfo.m_minElevation), item.m_altitude);
}
m_statistics.AddGpsInfoPoint(item);
m_items.emplace_back(item);
++added;
@ -106,7 +85,7 @@ std::pair<size_t, size_t> GpsTrackCollection::Clear(bool resetIds)
m_items.clear();
m_items.shrink_to_fit();
m_trackInfo = {};
m_statistics = {};
if (resetIds)
m_lastId = 0;

View file

@ -2,21 +2,14 @@
#include "platform/location.hpp"
#include "map/track_statistics.hpp"
#include "map/elevation_info.hpp"
#include <deque>
#include <limits>
#include <utility>
#include <vector>
struct GpsTrackInfo
{
double m_length;
double m_duration;
uint32_t m_ascent;
uint32_t m_descent;
int16_t m_minElevation;
int16_t m_maxElevation;
};
class GpsTrackCollection final
{
public:
@ -46,7 +39,8 @@ public:
/// Returns number of items in the collection
size_t GetSize() const;
GpsTrackInfo GetTrackInfo() const { return m_trackInfo; }
/// Returns track statistics.
const TrackStatistics GetTrackStatistics() const { return m_statistics; }
/// Enumerates items in the collection.
/// @param f - callable object, which is called with params - item and item id,
@ -72,5 +66,5 @@ private:
std::deque<TItem> m_items; // asc. sorted by timestamp
size_t m_lastId;
GpsTrackInfo m_trackInfo;
TrackStatistics m_statistics;
};

View file

@ -75,6 +75,11 @@ size_t GpsTracker::GetTrackSize() const
return m_track.GetSize();
}
TrackStatistics GpsTracker::GetTrackStatistics() const
{
return m_track.GetTrackStatistics();
}
void GpsTracker::Connect(TGpsTrackDiffCallback const & fn)
{
m_track.SetCallback(fn);

View file

@ -17,12 +17,12 @@ public:
bool IsEmpty() const;
size_t GetTrackSize() const;
GpsTrackInfo GetTrackInfo() const { return m_track.GetTrackInfo(); }
TrackStatistics GetTrackStatistics() const;
using TGpsTrackDiffCallback =
std::function<void(std::vector<std::pair<size_t, location::GpsInfo>> && toAdd,
std::pair<size_t, size_t> const & toRemove,
GpsTrackInfo const & trackInfo)>;
TrackStatistics const & trackStatistics)>;
void Connect(TGpsTrackDiffCallback const & fn);
void Disconnect();

View file

@ -18,6 +18,7 @@ set(SRC
transliteration_test.cpp
working_time_tests.cpp
elevation_info_tests.cpp
track_statistics_tests.cpp
)
omim_add_test(${PROJECT_NAME} ${SRC} REQUIRE_QT REQUIRE_SERVER)

View file

@ -7,18 +7,24 @@
#include "kml/types.hpp"
namespace geometry
namespace elevation_info_tests
{
using namespace geometry;
using namespace location;
GpsInfo const BuildGpsInfo(double latitude, double longitude, double altitude)
{
GpsInfo gpsInfo;
gpsInfo.m_latitude = latitude;
gpsInfo.m_longitude = longitude;
gpsInfo.m_altitude = altitude;
return gpsInfo;
}
UNIT_TEST(ElevationInfo_EmptyMultiGeometry)
{
ElevationInfo ei;
TEST_EQUAL(0, ei.GetSize(), ());
TEST_EQUAL(0, ei.GetAscent(), ());
TEST_EQUAL(0, ei.GetDescent(), ());
TEST_EQUAL(ei.GetMinAltitude(), kDefaultAltitudeMeters, ());
TEST_EQUAL(ei.GetMaxAltitude(), kDefaultAltitudeMeters, ());
}
UNIT_TEST(ElevationInfo_FromMultiGeometry)
@ -32,13 +38,9 @@ UNIT_TEST(ElevationInfo_FromMultiGeometry)
point2,
point3
});
ElevationInfo ei(geometry);
ElevationInfo ei(geometry.m_lines);
TEST_EQUAL(3, ei.GetSize(), ());
TEST_EQUAL(ei.GetMinAltitude(), 50, ());
TEST_EQUAL(ei.GetMaxAltitude(), 150, ());
TEST_EQUAL(ei.GetAscent(), 50, ()); // Ascent from 100 -> 150
TEST_EQUAL(ei.GetDescent(), 100, ()); // Descent from 150 -> 50
double distance = 0;
TEST_EQUAL(ei.GetPoints()[0].m_distance, distance, ());
@ -48,23 +50,6 @@ UNIT_TEST(ElevationInfo_FromMultiGeometry)
TEST_EQUAL(ei.GetPoints()[2].m_distance, distance, ());
}
UNIT_TEST(ElevationInfo_NoAltitudePoints)
{
kml::MultiGeometry geometry;
geometry.AddLine({
PointWithAltitude({0.0, 0.0}),
PointWithAltitude({1.0, 1.0}),
PointWithAltitude({2.0, 2.0})
});
ElevationInfo ei(geometry);
TEST_EQUAL(3, ei.GetSize(), ());
TEST_EQUAL(ei.GetMinAltitude(), kDefaultAltitudeMeters, ());
TEST_EQUAL(ei.GetMaxAltitude(), kDefaultAltitudeMeters, ());
TEST_EQUAL(ei.GetAscent(), 0, ());
TEST_EQUAL(ei.GetDescent(), 0, ());
}
UNIT_TEST(ElevationInfo_MultipleLines)
{
kml::MultiGeometry geometry;
@ -82,32 +67,28 @@ UNIT_TEST(ElevationInfo_MultipleLines)
PointWithAltitude({4.0, 4.0}, 200),
PointWithAltitude({5.0, 5.0}, 250)
});
ElevationInfo ei(geometry);
ElevationInfo ei(geometry.m_lines);
TEST_EQUAL(8, ei.GetSize(), ());
TEST_EQUAL(ei.GetMinAltitude(), 50, ());
TEST_EQUAL(ei.GetMaxAltitude(), 250, ());
TEST_EQUAL(ei.GetAscent(), 125, ()); // Ascent from 100 -> 150, 50 -> 75, 200 -> 250
TEST_EQUAL(ei.GetDescent(), 25, ()); // Descent from 150 -> 140, 75 -> 60
}
UNIT_TEST(ElevationInfo_SegmentDistances)
{
kml::MultiGeometry geometry;
geometry.AddLine({
geometry::PointWithAltitude({0.0, 0.0}),
geometry::PointWithAltitude({1.0, 0.0})
PointWithAltitude({0.0, 0.0}),
PointWithAltitude({1.0, 0.0})
});
geometry.AddLine({
geometry::PointWithAltitude({2.0, 0.0}),
geometry::PointWithAltitude({3.0, 0.0})
PointWithAltitude({2.0, 0.0}),
PointWithAltitude({3.0, 0.0})
});
geometry.AddLine({
geometry::PointWithAltitude({4.0, 0.0}),
geometry::PointWithAltitude({5.0, 0.0})
PointWithAltitude({4.0, 0.0}),
PointWithAltitude({5.0, 0.0})
});
ElevationInfo ei(geometry);
ElevationInfo ei(geometry.m_lines);
auto const & segmentDistances = ei.GetSegmentsDistances();
auto const points = ei.GetPoints();
@ -116,21 +97,23 @@ UNIT_TEST(ElevationInfo_SegmentDistances)
TEST_EQUAL(segmentDistances[1], ei.GetPoints()[4].m_distance, ());
}
UNIT_TEST(ElevationInfo_PositiveAndNegativeAltitudes)
UNIT_TEST(ElevationInfo_BuildWithGpsPoints)
{
kml::MultiGeometry geometry;
geometry.AddLine({
PointWithAltitude({0.0, 0.0}, -10),
PointWithAltitude({1.0, 1.0}, 20),
PointWithAltitude({2.0, 2.0}, -5),
PointWithAltitude({3.0, 3.0}, 15)
auto ei = ElevationInfo();
ei.AddGpsPoints({
BuildGpsInfo(0.0, 0.0, 0),
BuildGpsInfo(1.0, 1.0, 50),
BuildGpsInfo(2.0, 2.0, 100),
});
ElevationInfo ei(geometry);
ei.AddGpsPoints({
BuildGpsInfo(3.0, 3.0, -50)
});
ei.AddGpsPoints({
BuildGpsInfo(4.0, 4.0, 0)
});
ei.AddGpsPoints({});
TEST_EQUAL(4, ei.GetSize(), ());
TEST_EQUAL(ei.GetMinAltitude(), -10, ());
TEST_EQUAL(ei.GetMaxAltitude(), 20, ());
TEST_EQUAL(ei.GetAscent(), 50, ()); // Ascent from -10 -> 20 and -5 -> 15
TEST_EQUAL(ei.GetDescent(), 25, ()); // Descent from 20 -> -5
TEST_EQUAL(5, ei.GetSize(), ());
TEST_EQUAL(ei.GetSegmentsDistances().size(), 0, ());
}
} // namespace geometry
} // namespace elevation_info_testa

View file

@ -0,0 +1,185 @@
#include "testing/testing.hpp"
#include "map/track_statistics.hpp"
#include "geometry/point_with_altitude.hpp"
#include "geometry/mercator.hpp"
#include "kml/types.hpp"
namespace track_statistics_tests
{
using namespace geometry;
using namespace location;
GpsInfo const BuildGpsInfo(double latitude, double longitude, double altitude, double timestamp = 0)
{
GpsInfo gpsInfo;
gpsInfo.m_latitude = latitude;
gpsInfo.m_longitude = longitude;
gpsInfo.m_altitude = altitude;
gpsInfo.m_timestamp = timestamp;
return gpsInfo;
}
UNIT_TEST(TrackStatistics_EmptyMultiGeometry)
{
TrackStatistics ts;
TEST_EQUAL(0, ts.m_ascent, ());
TEST_EQUAL(0, ts.m_descent, ());
TEST_EQUAL(ts.m_minElevation, kDefaultAltitudeMeters, ());
TEST_EQUAL(ts.m_maxElevation, kDefaultAltitudeMeters, ());
}
UNIT_TEST(TrackStatistics_FromMultiGeometry)
{
kml::MultiGeometry geometry;
auto const point1 = PointWithAltitude({0.0, 0.0}, 100);
auto const point2 = PointWithAltitude({1.0, 1.0}, 150);
auto const point3 = PointWithAltitude({2.0, 2.0}, 50);
geometry.AddLine({
point1,
point2,
point3
});
geometry.AddTimestamps({
0,
1,
2
});
auto const ts = TrackStatistics(geometry);
TEST_EQUAL(ts.m_minElevation, 50, ());
TEST_EQUAL(ts.m_maxElevation, 150, ());
TEST_EQUAL(ts.m_ascent, 50, ()); // Ascent from 100 -> 150
TEST_EQUAL(ts.m_descent, 100, ()); // Descent from 150 -> 50
TEST_EQUAL(ts.m_duration, 2, ());
double distance = 0;
distance += mercator::DistanceOnEarth(point1, point2);
distance += mercator::DistanceOnEarth(point2, point3);
TEST_EQUAL(ts.m_length, distance, ());
}
UNIT_TEST(TrackStatistics_NoAltitudeAndTimestampPoints)
{
kml::MultiGeometry geometry;
geometry.AddLine({
PointWithAltitude({0.0, 0.0}),
PointWithAltitude({1.0, 1.0}),
PointWithAltitude({2.0, 2.0})
});
auto const ts = TrackStatistics(geometry);
TEST_EQUAL(ts.m_minElevation, kDefaultAltitudeMeters, ());
TEST_EQUAL(ts.m_maxElevation, kDefaultAltitudeMeters, ());
TEST_EQUAL(ts.m_ascent, 0, ());
TEST_EQUAL(ts.m_descent, 0, ());
TEST_EQUAL(ts.m_duration, 0, ());
}
UNIT_TEST(TrackStatistics_MultipleLines)
{
kml::MultiGeometry geometry;
geometry.AddLine({
PointWithAltitude({0.0, 0.0}, 100),
PointWithAltitude({1.0, 1.0}, 150),
PointWithAltitude({1.0, 1.0}, 140)
});
geometry.AddTimestamps({
0,
1,
2
});
geometry.AddLine({
PointWithAltitude({2.0, 2.0}, 50),
PointWithAltitude({3.0, 3.0}, 75),
PointWithAltitude({3.0, 3.0}, 60)
});
geometry.AddTimestamps({
0,
0,
0
});
geometry.AddLine({
PointWithAltitude({4.0, 4.0}, 200),
PointWithAltitude({5.0, 5.0}, 250)
});
geometry.AddTimestamps({
4,
5
});
auto const ts = TrackStatistics(geometry);
TEST_EQUAL(ts.m_minElevation, 50, ());
TEST_EQUAL(ts.m_maxElevation, 250, ());
TEST_EQUAL(ts.m_ascent, 125, ()); // Ascent from 100 -> 150, 50 -> 75, 200 -> 250
TEST_EQUAL(ts.m_descent, 25, ()); // Descent from 150 -> 140, 75 -> 60
TEST_EQUAL(ts.m_duration, 3, ());
}
UNIT_TEST(TrackStatistics_WithGpsPoints)
{
const std::vector<std::vector<GpsInfo>> pointsData = {
{ BuildGpsInfo(0.0, 0.0, 0, 0),
BuildGpsInfo(1.0, 1.0, 50, 1),
BuildGpsInfo(2.0, 2.0, 100, 2)
},
{
BuildGpsInfo(3.0, 3.0, -50, 5)
},
{
BuildGpsInfo(4.0, 4.0, 0, 10)
}
};
TrackStatistics ts;
for (auto const & pointsList : pointsData)
{
for (auto const & point : pointsList)
ts.AddGpsInfoPoint(point);
}
TEST_EQUAL(ts.m_minElevation, -50, ());
TEST_EQUAL(ts.m_maxElevation, 100, ());
TEST_EQUAL(ts.m_ascent, 150, ()); // Ascent from 0 -> 50, 50 -> 100, -50 -> 0
TEST_EQUAL(ts.m_descent, 150, ()); // Descent from 100 -> -50
TEST_EQUAL(ts.m_duration, 10, ());
}
UNIT_TEST(TrackStatistics_PositiveAndNegativeAltitudes)
{
kml::MultiGeometry geometry;
geometry.AddLine({
PointWithAltitude({0.0, 0.0}, -10),
PointWithAltitude({1.0, 1.0}, 20),
PointWithAltitude({2.0, 2.0}, -5),
PointWithAltitude({3.0, 3.0}, 15)
});
auto const ts = TrackStatistics(geometry);
TEST_EQUAL(ts.m_minElevation, -10, ());
TEST_EQUAL(ts.m_maxElevation, 20, ());
TEST_EQUAL(ts.m_ascent, 50, ()); // Ascent from -10 -> 20 and -5 -> 15
TEST_EQUAL(ts.m_descent, 25, ()); // Descent from 20 -> -5
}
UNIT_TEST(TrackStatistics_SmallAltitudeDelta)
{
const std::vector<GpsInfo> points = {
BuildGpsInfo(0.0, 0.0, 0),
BuildGpsInfo(1.0, 1.0, 0.2),
BuildGpsInfo(2.0, 2.0, 0.4),
BuildGpsInfo(3.0, 3.0, 0.6),
BuildGpsInfo(4.0, 4.0, 0.8),
BuildGpsInfo(5.0, 5.0, 1.0)
};
TrackStatistics ts;
for (auto const & point : points)
ts.AddGpsInfoPoint(point);
TEST_EQUAL(ts.m_minElevation, 0, ());
TEST_EQUAL(ts.m_maxElevation, 1.0, ());
TEST_EQUAL(ts.m_ascent, 1.0, ());
TEST_EQUAL(ts.m_descent, 0, ());
}
} // namespace track_statistics_tests

View file

@ -8,24 +8,6 @@
#include <utility>
namespace
{
double GetLengthInMeters(kml::MultiGeometry::LineT const & points, size_t pointIndex)
{
CHECK_LESS(pointIndex, points.size(), (pointIndex, points.size()));
double length = 0.0;
for (size_t i = 1; i <= pointIndex; ++i)
{
auto const & pt1 = points[i - 1].GetPoint();
auto const & pt2 = points[i].GetPoint();
auto const segmentLength = mercator::DistanceOnEarth(pt1, pt2);
length += segmentLength;
}
return length;
}
} // namespace
Track::Track(kml::TrackData && data)
: Base(data.m_id == kml::kInvalidTrackId ? UserMarkIdStorage::Instance().GetNextTrackId() : data.m_id)
, m_data(std::move(data))
@ -120,13 +102,7 @@ m2::RectD Track::GetLimitRect() const
double Track::GetLengthMeters() const
{
if (m_interactionData)
return m_interactionData->m_lengths.back().back();
double len = 0;
for (auto const & line : m_data.m_geometry.m_lines)
len += GetLengthInMeters(line, line.size() - 1);
return len;
return GetStatistics().m_length;
}
double Track::GetLengthMetersImpl(size_t lineIndex, size_t ptIndex) const
@ -236,25 +212,23 @@ kml::MultiGeometry::LineT Track::GetGeometry() const
return geometry;
}
TrackStatistics Track::GetStatistics() const
{
if (!m_trackStatistics.has_value())
m_trackStatistics = TrackStatistics(m_data.m_geometry);
return m_trackStatistics.value();
}
std::optional<ElevationInfo> Track::GetElevationInfo() const
{
if (!HasAltitudes())
return std::nullopt;
if (!m_elevationInfo)
m_elevationInfo = ElevationInfo(GetData().m_geometry);
m_elevationInfo = ElevationInfo(GetData().m_geometry.m_lines);
return m_elevationInfo;
}
double Track::GetDurationInSeconds() const
{
double duration = 0.0;
if (!m_data.m_geometry.HasTimestamps())
return duration;
for (size_t i = 0; i < m_data.m_geometry.m_timestamps.size(); ++i)
{
ASSERT(m_data.m_geometry.HasTimestampsFor(i), ());
auto const & timestamps = m_data.m_geometry.m_timestamps[i];
duration += timestamps.back() - timestamps.front();
}
return duration;
return GetStatistics().m_duration;
}

View file

@ -3,6 +3,7 @@
#include "kml/types.hpp"
#include "map/elevation_info.hpp"
#include "map/track_statistics.hpp"
#include "drape_frontend/user_marks_provider.hpp"
@ -31,6 +32,7 @@ public:
m2::RectD GetLimitRect() const;
double GetLengthMeters() const;
double GetDurationInSeconds() const;
TrackStatistics GetStatistics() const;
std::optional<ElevationInfo> GetElevationInfo() const;
std::pair<m2::PointD, double> GetCenterPoint() const;
@ -80,6 +82,7 @@ private:
kml::TrackData m_data;
kml::MarkGroupId m_groupID = kml::kInvalidMarkGroupId;
mutable std::optional<TrackStatistics> m_trackStatistics;
mutable std::optional<ElevationInfo> m_elevationInfo;
struct InteractionData

108
map/track_statistics.cpp Normal file
View file

@ -0,0 +1,108 @@
#include "map/track_statistics.hpp"
#include "geometry/mercator.hpp"
#include "base/logging.hpp"
using namespace geometry;
double constexpr kInvalidTimestamp = std::numeric_limits<double>::min();
PointWithAltitude const kInvalidPoint = {m2::PointD::Zero(), kInvalidAltitude};
TrackStatistics::TrackStatistics()
: m_length(0),
m_duration(0),
m_ascent(0),
m_descent(0),
m_minElevation(kDefaultAltitudeMeters),
m_maxElevation(kDefaultAltitudeMeters),
m_previousPoint(kInvalidPoint),
m_previousTimestamp(kInvalidTimestamp)
{}
TrackStatistics::TrackStatistics(kml::MultiGeometry const & geometry)
: TrackStatistics()
{
for (auto const & line : geometry.m_lines)
AddPoints(line, true);
if (geometry.HasTimestamps())
{
for (size_t i = 0; i < geometry.m_timestamps.size(); ++i)
{
ASSERT(geometry.HasTimestampsFor(i), ());
AddTimestamps(geometry.m_timestamps[i], true);
}
}
}
void TrackStatistics::AddGpsInfoPoint(location::GpsInfo const & point)
{
auto const pointWithAltitude = geometry::PointWithAltitude(mercator::FromLatLon(point.m_latitude, point.m_longitude), point.m_altitude);
AddPoints({pointWithAltitude}, false);
AddTimestamps({point.m_timestamp}, false);
}
void TrackStatistics::AddPoints(kml::MultiGeometry::LineT const & line, bool isNewSegment)
{
if (line.empty())
return;
size_t startIndex = 0;
if (HasNoPoints() || isNewSegment)
{
InitializeNewSegment(line[0]);
startIndex = 1;
}
ProcessPoints(line, startIndex);
}
void TrackStatistics::InitializeNewSegment(PointWithAltitude const & firstPoint)
{
auto const altitude = firstPoint.GetAltitude();
bool const hasNoPoints = HasNoPoints();
m_minElevation = hasNoPoints ? altitude : std::min(m_minElevation, altitude);
m_maxElevation = hasNoPoints ? altitude : std::max(m_maxElevation, altitude);
m_previousPoint = firstPoint;
}
void TrackStatistics::ProcessPoints(kml::MultiGeometry::LineT const & points, size_t startIndex)
{
for (size_t i = startIndex; i < points.size(); ++i)
{
auto const & point = points[i];
auto const pointAltitude = point.GetAltitude();
m_minElevation = std::min(m_minElevation, pointAltitude);
m_maxElevation = std::max(m_maxElevation, pointAltitude);
auto const deltaAltitude = pointAltitude - m_previousPoint.GetAltitude();
if (deltaAltitude > 0)
m_ascent += deltaAltitude;
else
m_descent -= deltaAltitude;
m_length += mercator::DistanceOnEarth(m_previousPoint.GetPoint(), point.GetPoint());
m_previousPoint = point;
}
}
void TrackStatistics::AddTimestamps(kml::MultiGeometry::TimeT const & timestamps, bool isNewSegment)
{
if (timestamps.empty())
return;
if (m_previousTimestamp == kInvalidTimestamp)
m_previousTimestamp = timestamps.front();
auto const baseTimestamp = isNewSegment ? timestamps.front() : m_previousTimestamp;
m_duration += timestamps.back() - baseTimestamp;
m_previousTimestamp = timestamps.back();
}
bool TrackStatistics::HasNoPoints() const
{
return m_previousPoint == kInvalidPoint;
}

29
map/track_statistics.hpp Normal file
View file

@ -0,0 +1,29 @@
#pragma once
#include "platform/location.hpp"
#include "kml/types.hpp"
struct TrackStatistics
{
TrackStatistics();
explicit TrackStatistics(kml::MultiGeometry const & geometry);
double m_length;
double m_duration;
double m_ascent;
double m_descent;
int16_t m_minElevation;
int16_t m_maxElevation;
void AddGpsInfoPoint(location::GpsInfo const & point);
private:
void AddPoints(kml::MultiGeometry::LineT const & line, bool isNewSegment);
void AddTimestamps(kml::MultiGeometry::TimeT const & timestamps, bool isNewSegment);
void InitializeNewSegment(geometry::PointWithAltitude const & firstPoint);
void ProcessPoints(kml::MultiGeometry::LineT const & points, size_t startIndex);
bool HasNoPoints() const;
geometry::PointWithAltitude m_previousPoint;
double m_previousTimestamp;
};

View file

@ -92,6 +92,9 @@
BBFC7E3A202D29C000531BE7 /* user_mark_layer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BBFC7E38202D29BF00531BE7 /* user_mark_layer.cpp */; };
BBFC7E3B202D29C000531BE7 /* user_mark_layer.hpp in Headers */ = {isa = PBXBuildFile; fileRef = BBFC7E39202D29BF00531BE7 /* user_mark_layer.hpp */; };
ED49D74C2CEF3D69004AF27E /* elevation_info_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED49D74B2CEF3CE3004AF27E /* elevation_info_tests.cpp */; };
ED85D1CC2D5F4B5B00D8075D /* track_statistics.hpp in Headers */ = {isa = PBXBuildFile; fileRef = ED85D1CB2D5F4B5B00D8075D /* track_statistics.hpp */; };
ED85D1CE2D5F4B7200D8075D /* track_statistics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED85D1CD2D5F4B6600D8075D /* track_statistics.cpp */; };
ED85D1D02D5F508700D8075D /* track_statistics_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED85D1CF2D5F508700D8075D /* track_statistics_tests.cpp */; };
F6B283031C1B03320081957A /* gps_track_collection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F6B282FB1C1B03320081957A /* gps_track_collection.cpp */; };
F6B283041C1B03320081957A /* gps_track_collection.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F6B282FC1C1B03320081957A /* gps_track_collection.hpp */; };
F6B283051C1B03320081957A /* gps_track_filter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F6B282FD1C1B03320081957A /* gps_track_filter.cpp */; };
@ -240,6 +243,9 @@
BBFC7E38202D29BF00531BE7 /* user_mark_layer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = user_mark_layer.cpp; sourceTree = "<group>"; };
BBFC7E39202D29BF00531BE7 /* user_mark_layer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = user_mark_layer.hpp; sourceTree = "<group>"; };
ED49D74B2CEF3CE3004AF27E /* elevation_info_tests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = elevation_info_tests.cpp; sourceTree = "<group>"; };
ED85D1CB2D5F4B5B00D8075D /* track_statistics.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = track_statistics.hpp; sourceTree = "<group>"; };
ED85D1CD2D5F4B6600D8075D /* track_statistics.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = track_statistics.cpp; sourceTree = "<group>"; };
ED85D1CF2D5F508700D8075D /* track_statistics_tests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = track_statistics_tests.cpp; sourceTree = "<group>"; };
F6B282FB1C1B03320081957A /* gps_track_collection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = gps_track_collection.cpp; sourceTree = "<group>"; };
F6B282FC1C1B03320081957A /* gps_track_collection.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = gps_track_collection.hpp; sourceTree = "<group>"; };
F6B282FD1C1B03320081957A /* gps_track_filter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = gps_track_filter.cpp; sourceTree = "<group>"; };
@ -355,6 +361,7 @@
BB421D6A1E8C0026005BFA4D /* transliteration_test.cpp */,
674A2A351B27011A001A525C /* working_time_tests.cpp */,
ED49D74B2CEF3CE3004AF27E /* elevation_info_tests.cpp */,
ED85D1CF2D5F508700D8075D /* track_statistics_tests.cpp */,
);
name = map_tests;
path = ../../map/map_tests;
@ -444,6 +451,8 @@
BB1C0194241BF73C0067FD5C /* track_mark.hpp */,
6753462C1A4054E800A0A8C3 /* track.cpp */,
6753462D1A4054E800A0A8C3 /* track.hpp */,
ED85D1CD2D5F4B6600D8075D /* track_statistics.cpp */,
ED85D1CB2D5F4B5B00D8075D /* track_statistics.hpp */,
347B60741DD9926D0050FA24 /* traffic_manager.cpp */,
347B60751DD9926D0050FA24 /* traffic_manager.hpp */,
BB4E5F201FCC663700A77250 /* transit */,
@ -513,6 +522,7 @@
675346651A4054E800A0A8C3 /* framework.hpp in Headers */,
BBA014B120754997007402E4 /* user_mark_id_storage.hpp in Headers */,
674A2A381B2715FB001A525C /* osm_opening_hours.hpp in Headers */,
ED85D1CC2D5F4B5B00D8075D /* track_statistics.hpp in Headers */,
3DEE1ADF21EE03B400054A91 /* power_manager.hpp in Headers */,
F6D2CE7F1EDEB7F500636DFD /* routing_manager.hpp in Headers */,
670E39411C46C5C700E9C0A6 /* gps_tracker.hpp in Headers */,
@ -653,6 +663,7 @@
679624B21D1017DB00AE4E3C /* mwm_set_test.cpp in Sources */,
679624AD1D1017DB00AE4E3C /* address_tests.cpp in Sources */,
67F183791BD5045700AB1840 /* kmz_unarchive_test.cpp in Sources */,
ED85D1D02D5F508700D8075D /* track_statistics_tests.cpp in Sources */,
FAA8387326BB3C09002E54C6 /* check_mwms.cpp in Sources */,
679624AE1D1017DB00AE4E3C /* feature_getters_tests.cpp in Sources */,
67F1837A1BD5045700AB1840 /* mwm_url_tests.cpp in Sources */,
@ -700,6 +711,7 @@
F6B283071C1B03320081957A /* gps_track_storage.cpp in Sources */,
670E39401C46C5C700E9C0A6 /* gps_tracker.cpp in Sources */,
BBA014B220754997007402E4 /* user_mark_id_storage.cpp in Sources */,
ED85D1CE2D5F4B7200D8075D /* track_statistics.cpp in Sources */,
6753464A1A4054E800A0A8C3 /* bookmark.cpp in Sources */,
45580ABE1E2CBD5E00CD535D /* benchmark_tools.cpp in Sources */,
3DA5723220C195ED007BDE27 /* everywhere_search_callback.cpp in Sources */,