Highlight steep parts of a route
Signed-off-by: Ilya Shakhat <shakhat@gmail.com>
This commit is contained in:
parent
5a4e97b818
commit
53a5ba04a9
3 changed files with 72 additions and 20 deletions
|
@ -112,7 +112,8 @@ void ReflectChartData(vector<double> & chartData)
|
|||
|
||||
bool NormalizeChartData(vector<double> const & distanceDataM,
|
||||
geometry::Altitudes const & altitudeDataM, size_t resultPointCount,
|
||||
vector<double> & uniformAltitudeDataM)
|
||||
vector<double> & uniformAltitudeDataM,
|
||||
vector<double> & uniformSlopeDataM)
|
||||
{
|
||||
double constexpr kEpsilon = 1e-6;
|
||||
|
||||
|
@ -134,11 +135,13 @@ bool NormalizeChartData(vector<double> const & distanceDataM,
|
|||
return true;
|
||||
}
|
||||
|
||||
auto const calculateAltitude = [&](double distFormStartM) {
|
||||
using TAltitudeSlope = pair<double, double>;
|
||||
|
||||
auto const calculateAltitudeSlope = [&](double distFormStartM) {
|
||||
if (distFormStartM <= distanceDataM.front())
|
||||
return static_cast<double>(altitudeDataM.front());
|
||||
return TAltitudeSlope(static_cast<double>(altitudeDataM.front()), 0);
|
||||
if (distFormStartM >= distanceDataM.back())
|
||||
return static_cast<double>(altitudeDataM.back());
|
||||
return TAltitudeSlope(static_cast<double>(altitudeDataM.back()), 0);
|
||||
|
||||
auto const lowerIt = lower_bound(distanceDataM.cbegin(), distanceDataM.cend(), distFormStartM);
|
||||
size_t const nextPointIdx = distance(distanceDataM.cbegin(), lowerIt);
|
||||
|
@ -146,20 +149,24 @@ bool NormalizeChartData(vector<double> const & distanceDataM,
|
|||
size_t const prevPointIdx = nextPointIdx - 1;
|
||||
|
||||
if (base::AlmostEqualAbs(distanceDataM[prevPointIdx], distanceDataM[nextPointIdx], kEpsilon))
|
||||
return static_cast<double>(altitudeDataM[prevPointIdx]);
|
||||
return TAltitudeSlope(static_cast<double>(altitudeDataM[prevPointIdx]), 0);
|
||||
|
||||
double const k = (altitudeDataM[nextPointIdx] - altitudeDataM[prevPointIdx]) /
|
||||
(distanceDataM[nextPointIdx] - distanceDataM[prevPointIdx]);
|
||||
return static_cast<double>(altitudeDataM[prevPointIdx]) +
|
||||
k * (distFormStartM - distanceDataM[prevPointIdx]);
|
||||
return TAltitudeSlope(static_cast<double>(altitudeDataM[prevPointIdx]) +
|
||||
k * (distFormStartM - distanceDataM[prevPointIdx]), k);
|
||||
};
|
||||
|
||||
double const routeLenM = distanceDataM.back();
|
||||
uniformAltitudeDataM.resize(resultPointCount);
|
||||
uniformSlopeDataM.resize(resultPointCount);
|
||||
double const stepLenM = resultPointCount <= 1 ? 0.0 : routeLenM / (resultPointCount - 1);
|
||||
|
||||
for (size_t i = 0; i < resultPointCount; ++i)
|
||||
uniformAltitudeDataM[i] = calculateAltitude(static_cast<double>(i) * stepLenM);
|
||||
for (size_t i = 0; i < resultPointCount; ++i) {
|
||||
auto altitudeSlope = calculateAltitudeSlope(static_cast<double>(i) * stepLenM);
|
||||
uniformAltitudeDataM[i] = altitudeSlope.first;
|
||||
uniformSlopeDataM[i] = altitudeSlope.second;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -212,6 +219,7 @@ bool GenerateYAxisChartData(uint32_t height, double minMetersPerPxl,
|
|||
}
|
||||
|
||||
bool GenerateChartByPoints(uint32_t width, uint32_t height, vector<m2::PointD> const & geometry,
|
||||
vector<double> const & slopes,
|
||||
MapStyle mapStyle, vector<uint8_t> & frameBuffer)
|
||||
{
|
||||
frameBuffer.clear();
|
||||
|
@ -263,6 +271,39 @@ bool GenerateChartByPoints(uint32_t width, uint32_t height, vector<m2::PointD> c
|
|||
agg::scanline32_p8 scanline;
|
||||
agg::render_scanlines_aa_solid(rasterizer, scanline, baseRenderer, kCurveColor);
|
||||
|
||||
// Draw slopes
|
||||
for (size_t i = 1; i < geometry.size(); ++i) {
|
||||
auto x = geometry[i].x;
|
||||
auto y = geometry[i].y;
|
||||
auto slope = abs(slopes[i]);
|
||||
|
||||
if (slope < 0.05) {
|
||||
continue;
|
||||
}
|
||||
|
||||
agg::path_storage segment;
|
||||
segment.move_to(x, y);
|
||||
segment.line_to(x, static_cast<double>(height));
|
||||
agg::conv_stroke<agg::path_storage> strokeSegment(segment);
|
||||
strokeSegment.width(2.0);
|
||||
|
||||
rasterizer.add_path(strokeSegment);
|
||||
|
||||
agg::rgba8 color;
|
||||
// palette is generated by mycolor.space from the base color kCurveColor
|
||||
if (slope < 0.1)
|
||||
color = agg::rgba8(0x62, 0x83, 0xDC, 20);
|
||||
else if (slope < 0.2)
|
||||
color = agg::rgba8(0x7F, 0x71, 0xC3, 40);
|
||||
else if (slope < 0.3)
|
||||
color = agg::rgba8(0x8E, 0x60, 0xA7, 60);
|
||||
else if (slope < 0.4)
|
||||
color = agg::rgba8(0x93, 0x51, 0x8B, 80);
|
||||
else
|
||||
color = agg::rgba8(0x90, 0x44, 0x6F, 100);
|
||||
agg::render_scanlines_aa_solid(rasterizer, scanline, baseRenderer, color);
|
||||
}
|
||||
|
||||
// Chart line.
|
||||
TPath path_adaptor(geometry, false);
|
||||
TStroke stroke(path_adaptor);
|
||||
|
@ -287,7 +328,8 @@ bool GenerateChart(uint32_t width, uint32_t height, vector<double> const & dista
|
|||
}
|
||||
|
||||
vector<double> uniformAltitudeDataM;
|
||||
if (!NormalizeChartData(distanceDataM, altitudeDataM, width, uniformAltitudeDataM))
|
||||
vector<double> uniformSlopeDataM;
|
||||
if (!NormalizeChartData(distanceDataM, altitudeDataM, width, uniformAltitudeDataM, uniformSlopeDataM))
|
||||
return false;
|
||||
|
||||
vector<double> yAxisDataPxl;
|
||||
|
@ -305,6 +347,6 @@ bool GenerateChart(uint32_t width, uint32_t height, vector<double> const & dista
|
|||
geometry[i] = m2::PointD(i * oneSegLenPix, yAxisDataPxl[i]);
|
||||
}
|
||||
|
||||
return GenerateChartByPoints(width, height, geometry, mapStyle, frameBuffer);
|
||||
return GenerateChartByPoints(width, height, geometry, uniformSlopeDataM, mapStyle, frameBuffer);
|
||||
}
|
||||
} // namespace maps
|
||||
|
|
|
@ -21,7 +21,8 @@ void ReflectChartData(std::vector<double> & chartData);
|
|||
/// This method is used to generalize and evenly distribute points of the chart.
|
||||
bool NormalizeChartData(std::vector<double> const & distanceDataM,
|
||||
geometry::Altitudes const & altitudeDataM, size_t resultPointCount,
|
||||
std::vector<double> & uniformAltitudeDataM);
|
||||
std::vector<double> & uniformAltitudeDataM,
|
||||
std::vector<double> & uniformSlopeDataM);
|
||||
|
||||
/// \brief fills |yAxisDataPxl|. |yAxisDataPxl| is formed to pevent displaying
|
||||
/// big waves on the chart in case of small deviation in absolute values in |yAxisData|.
|
||||
|
@ -42,7 +43,9 @@ bool GenerateYAxisChartData(uint32_t height, double minMetersPerPxl,
|
|||
/// \param frameBuffer is a vector for a result image. It's resized in this method.
|
||||
/// It's filled with RGBA(8888) image date.
|
||||
bool GenerateChartByPoints(uint32_t width, uint32_t height,
|
||||
std::vector<m2::PointD> const & geometry, MapStyle mapStyle,
|
||||
std::vector<m2::PointD> const & geometry,
|
||||
std::vector<double> const & slopes,
|
||||
MapStyle mapStyle,
|
||||
std::vector<uint8_t> & frameBuffer);
|
||||
|
||||
bool GenerateChart(uint32_t width, uint32_t height, std::vector<double> const & distanceDataM,
|
||||
|
|
|
@ -81,7 +81,8 @@ UNIT_TEST(NormalizeChartData_SmokeTest)
|
|||
geometry::Altitudes const altitudeDataM = {0, 0, 0};
|
||||
|
||||
vector<double> uniformAltitudeDataM;
|
||||
TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 2 /* resultPointCount */, uniformAltitudeDataM),
|
||||
vector<double> uniformSlopeDataM;
|
||||
TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 2 /* resultPointCount */, uniformAltitudeDataM, uniformSlopeDataM),
|
||||
());
|
||||
|
||||
vector<double> const expectedUniformAltitudeDataM = {0.0, 0.0};
|
||||
|
@ -94,7 +95,8 @@ UNIT_TEST(NormalizeChartData_NoResultPointTest)
|
|||
geometry::Altitudes const altitudeDataM = {0, 0, 0};
|
||||
|
||||
vector<double> uniformAltitudeDataM;
|
||||
TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 0 /* resultPointCount */, uniformAltitudeDataM),
|
||||
vector<double> uniformSlopeDataM;
|
||||
TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 0 /* resultPointCount */, uniformAltitudeDataM, uniformSlopeDataM),
|
||||
());
|
||||
|
||||
TEST(uniformAltitudeDataM.empty(), ());
|
||||
|
@ -106,7 +108,8 @@ UNIT_TEST(NormalizeChartData_NoPointTest)
|
|||
geometry::Altitudes const altitudeDataM = {};
|
||||
|
||||
vector<double> uniformAltitudeDataM;
|
||||
TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 2 /* resultPointCount */, uniformAltitudeDataM),
|
||||
vector<double> uniformSlopeDataM;
|
||||
TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 2 /* resultPointCount */, uniformAltitudeDataM, uniformSlopeDataM),
|
||||
());
|
||||
|
||||
TEST(uniformAltitudeDataM.empty(), ());
|
||||
|
@ -118,7 +121,8 @@ UNIT_TEST(NormalizeChartData_Test)
|
|||
geometry::Altitudes const altitudeDataM = {-9, 0, 9, 18};
|
||||
|
||||
vector<double> uniformAltitudeDataM;
|
||||
TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 10 /* resultPointCount */, uniformAltitudeDataM),
|
||||
vector<double> uniformSlopeDataM;
|
||||
TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 10 /* resultPointCount */, uniformAltitudeDataM, uniformSlopeDataM),
|
||||
());
|
||||
|
||||
vector<double> const expectedUniformAltitudeDataM = {-9.0, -6.0, -3.0, 0.0, 3.0,
|
||||
|
@ -158,11 +162,12 @@ UNIT_TEST(GenerateYAxisChartData_Test)
|
|||
UNIT_TEST(GenerateChartByPoints_NoGeometryTest)
|
||||
{
|
||||
vector<m2::PointD> const geometry = {};
|
||||
vector<double> const slopes = {};
|
||||
size_t constexpr width = 100;
|
||||
size_t constexpr height = 40;
|
||||
vector<uint8_t> frameBuffer;
|
||||
|
||||
TEST(maps::GenerateChartByPoints(width, height, geometry, MapStyleDefaultLight /* mapStyle */, frameBuffer), ());
|
||||
TEST(maps::GenerateChartByPoints(width, height, geometry, slopes, MapStyleDefaultLight /* mapStyle */, frameBuffer), ());
|
||||
TestAngleColors(width, height, frameBuffer, 255 /* expectedR */, 255 /* expectedG */,
|
||||
255 /* expectedB */, 0 /* expectedA */);
|
||||
}
|
||||
|
@ -170,11 +175,12 @@ UNIT_TEST(GenerateChartByPoints_NoGeometryTest)
|
|||
UNIT_TEST(GenerateChartByPoints_OnePointTest)
|
||||
{
|
||||
vector<m2::PointD> const geometry = {{20.0, 20.0}};
|
||||
vector<double> const slopes = {0.0};
|
||||
size_t constexpr width = 40;
|
||||
size_t constexpr height = 40;
|
||||
vector<uint8_t> frameBuffer;
|
||||
|
||||
TEST(maps::GenerateChartByPoints(width, height, geometry, MapStyleDefaultLight /* mapStyle */, frameBuffer), ());
|
||||
TEST(maps::GenerateChartByPoints(width, height, geometry, slopes, MapStyleDefaultLight /* mapStyle */, frameBuffer), ());
|
||||
TestAngleColors(width, height, frameBuffer, 255 /* expectedR */, 255 /* expectedG */,
|
||||
255 /* expectedB */, 0 /* expectedA */);
|
||||
}
|
||||
|
@ -182,12 +188,13 @@ UNIT_TEST(GenerateChartByPoints_OnePointTest)
|
|||
UNIT_TEST(GenerateChartByPoints_Test)
|
||||
{
|
||||
vector<m2::PointD> const geometry = {{0.0, 0.0}, {10.0, 10.0}};
|
||||
vector<double> const slopes = {1.0, 0.0};
|
||||
|
||||
size_t constexpr width = 40;
|
||||
size_t constexpr height = 40;
|
||||
vector<uint8_t> frameBuffer;
|
||||
|
||||
TEST(maps::GenerateChartByPoints(width, height, geometry, MapStyleDefaultLight /* mapStyle */, frameBuffer), ());
|
||||
TEST(maps::GenerateChartByPoints(width, height, geometry, slopes, MapStyleDefaultLight /* mapStyle */, frameBuffer), ());
|
||||
|
||||
TEST_EQUAL(frameBuffer.size(), width * height * kAltitudeChartBPP, ());
|
||||
TEST(IsColor(frameBuffer, 0 /* startColorIdx */, 30 /* expectedR */, 150 /* expectedG */,
|
||||
|
|
Reference in a new issue