Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,15 @@
project(kml_tests)
set(SRC
color_parser_tests.cpp
gpx_tests.cpp
minzoom_quadtree_tests.cpp
serdes_tests.cpp
tests_data.hpp
)
omim_add_test(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
kml
)

View file

@ -0,0 +1,19 @@
#include "testing/testing.hpp"
#include "kml/color_parser.hpp"
UNIT_TEST(ColorParser_Smoke)
{
auto const magenta = kml::ParseGarminColor("Magenta");
TEST(magenta, ());
TEST_EQUAL(magenta, kml::ParseOSMColor("magenta"), ());
TEST_EQUAL(magenta, kml::ParseHexColor("ff00ff"), ());
TEST_EQUAL(magenta, kml::ParseHexColor("#ff00ff"), ());
TEST_EQUAL(magenta, kml::ParseOSMColor("#f0f"), ());
TEST(!kml::ParseGarminColor("xxyyzz"), ());
TEST(!kml::ParseOSMColor("#xxyyzz"), ());
// Current implementation gives assert with default 0 channel value. I didn't change this.
// TEST(!kml::ParseHexColor("#xxyyzz"), ());
}

View file

@ -0,0 +1,431 @@
#include "testing/testing.hpp"
#include "kml/color_parser.hpp"
#include "kml/serdes_common.hpp"
#include "kml/serdes_gpx.hpp"
#include "geometry/mercator.hpp"
#include "coding/file_reader.hpp"
#include "platform/platform.hpp"
namespace gpx_tests
{
static kml::FileData LoadGpxFromString(std::string_view content)
{
TEST_NO_THROW(
{
kml::FileData dataFromText;
kml::DeserializerGpx(dataFromText).Deserialize(MemReader(content));
return dataFromText;
},
());
}
static kml::FileData LoadGpxFromFile(std::string const & file)
{
auto const fileName = GetPlatform().TestsDataPathForFile(file);
std::string text;
FileReader(fileName).ReadAsString(text);
return LoadGpxFromString(text);
}
static std::string ReadFile(char const * testFile)
{
auto const fileName = GetPlatform().TestsDataPathForFile(testFile);
std::string sourceFileText;
FileReader(fileName).ReadAsString(sourceFileText);
return sourceFileText;
}
static std::string Serialize(kml::FileData const & dataFromFile)
{
std::string resultBuffer;
MemWriter<decltype(resultBuffer)> sink(resultBuffer);
kml::gpx::SerializerGpx ser(dataFromFile);
ser.Serialize(sink);
return resultBuffer;
}
static std::string ReadFileAndSerialize(char const * testFile)
{
kml::FileData const dataFromFile = LoadGpxFromFile(testFile);
return Serialize(dataFromFile);
}
void ImportExportCompare(char const * testFile)
{
std::string const sourceFileText = ReadFile(testFile);
std::string const resultBuffer = ReadFileAndSerialize(testFile);
TEST_EQUAL(resultBuffer, sourceFileText, ());
}
void ImportExportCompare(char const * sourceFile, char const * destinationFile)
{
std::string const resultBuffer = ReadFileAndSerialize(sourceFile);
std::string const destinationFileText = ReadFile(destinationFile);
TEST_EQUAL(resultBuffer, destinationFileText, ());
}
UNIT_TEST(Gpx_ImportExport_Test)
{
ImportExportCompare("test_data/gpx/export_test.gpx");
}
UNIT_TEST(Gpx_ImportExportEmpty_Test)
{
ImportExportCompare("test_data/gpx/export_test_empty.gpx");
}
UNIT_TEST(Gpx_ColorMapExport_Test)
{
ImportExportCompare("test_data/gpx/color_map_src.gpx", "test_data/gpx/color_map_dst.gpx");
}
UNIT_TEST(Gpx_Test_Point_With_Valid_Timestamp)
{
std::string_view constexpr input = R"(<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.0">
<wpt lat="42.81025" lon="-1.65727">
<time>2022-09-05T08:39:39.3700Z</time>
<name>Waypoint 1</name>
</wpt>
)";
kml::FileData data;
kml::BookmarkData bookmarkData;
bookmarkData.m_name[kml::kDefaultLang] = "Waypoint 1";
bookmarkData.m_point = mercator::FromLatLon(42.81025, -1.65727);
bookmarkData.m_customName[kml::kDefaultLang] = "Waypoint 1";
bookmarkData.m_color = {kml::PredefinedColor::Red, 0};
data.m_bookmarksData.emplace_back(std::move(bookmarkData));
kml::FileData const dataFromText = LoadGpxFromString(input);
TEST_EQUAL(dataFromText, data, ());
}
UNIT_TEST(Gpx_Test_Point_With_Invalid_Timestamp)
{
std::string_view constexpr input = R"(<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.0">
<wpt lat="42.81025" lon="-1.65727">
<time>2022-09-05T08:39:39.3700X</time>
<name>Waypoint 1</name>
</wpt>
)";
kml::FileData const dataFromText = LoadGpxFromString(input);
TEST_EQUAL(dataFromText.m_bookmarksData.size(), 1, ());
}
UNIT_TEST(Gpx_Test_Track_Without_Timestamps)
{
auto const fileName = "test_data/gpx/track_without_timestamps.gpx";
kml::FileData const dataFromText = LoadGpxFromFile(fileName);
auto const & lines = dataFromText.m_tracksData[0].m_geometry.m_lines;
TEST_EQUAL(lines.size(), 2, ());
{
auto const & line = lines[0];
TEST_EQUAL(line.size(), 3, ());
TEST_EQUAL(line.back(), geometry::PointWithAltitude(mercator::FromLatLon(54.05293900056246, 25.72998046875), 0),
());
}
{
auto const & line = lines[1];
TEST_EQUAL(line.size(), 2, ());
TEST_EQUAL(line.back(), geometry::PointWithAltitude(mercator::FromLatLon(54.32933804825253, 25.136718750000004), 0),
());
}
// Also test default colors for tracks.
{
TEST_EQUAL(dataFromText.m_tracksData.size(), 1, ());
TEST_EQUAL(dataFromText.m_tracksData[0].m_layers.size(), 1, ());
auto const & layer = dataFromText.m_tracksData[0].m_layers[0];
TEST_EQUAL(layer.m_color.m_rgba, kml::kDefaultTrackColor, ());
TEST_EQUAL(layer.m_color.m_predefinedColor, kml::PredefinedColor::None, ());
TEST_EQUAL(layer.m_lineWidth, kml::kDefaultTrackWidth, ());
auto const & geometry = dataFromText.m_tracksData[0].m_geometry;
TEST_EQUAL(geometry.m_timestamps.size(), 2, ());
TEST(geometry.m_timestamps[0].empty(), ());
TEST(geometry.m_timestamps[1].empty(), ());
TEST(!geometry.HasTimestamps(), ());
TEST(!geometry.HasTimestampsFor(0), ());
TEST(!geometry.HasTimestampsFor(1), ());
}
}
UNIT_TEST(Gpx_Test_Track_With_Timestamps)
{
auto const fileName = "test_data/gpx/track_with_timestamps.gpx";
kml::FileData const dataFromText = LoadGpxFromFile(fileName);
auto const & geometry = dataFromText.m_tracksData[0].m_geometry;
TEST_EQUAL(geometry.m_lines.size(), 2, ());
TEST_EQUAL(geometry.m_timestamps.size(), 2, ());
TEST(geometry.IsValid(), ());
TEST(geometry.HasTimestamps(), ());
TEST(geometry.HasTimestampsFor(0), ());
TEST(geometry.HasTimestampsFor(1), ());
}
UNIT_TEST(Gpx_Test_Track_With_Timestamps_Mismatch)
{
auto const fileName = GetPlatform().TestsDataPathForFile("test_data/gpx/track_with_timestamps_broken.gpx");
std::string text;
FileReader(fileName).ReadAsString(text);
kml::FileData data;
kml::DeserializerGpx(data).Deserialize(MemReader(text));
TEST_EQUAL(data.m_tracksData.size(), 1, ());
TEST_EQUAL(data.m_tracksData[0].m_geometry.m_timestamps.size(), 2, ());
TEST(data.m_tracksData[0].m_geometry.HasTimestampsFor(0), ());
TEST(data.m_tracksData[0].m_geometry.HasTimestampsFor(1), ());
}
UNIT_TEST(Gpx_Altitude_Issues)
{
std::string_view constexpr input = R"(<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.0">
<trk>
<name>new</name>
<type>Cycling</type>
<trkseg>
<trkpt lat="1" lon="1"></trkpt>
<trkpt lat="2" lon="2"><ele>1.0</ele></trkpt>
<trkpt lat="3" lon="3"></trkpt>
<trkpt lat="4" lon="4"><ele>2.0</ele></trkpt>
<trkpt lat="5" lon="5"><ele>Wrong</ele></trkpt>
<trkpt lat="6" lon="6"><ele>3</ele></trkpt>
</trkseg>
</trk>
</gpx>
)";
kml::FileData const dataFromText = LoadGpxFromString(input);
auto const & line = dataFromText.m_tracksData[0].m_geometry.m_lines[0];
TEST_EQUAL(line.size(), 6, ());
TEST_EQUAL(line[0], geometry::PointWithAltitude(mercator::FromLatLon(1, 1), geometry::kInvalidAltitude), ());
TEST_EQUAL(line[1], geometry::PointWithAltitude(mercator::FromLatLon(2, 2), 1), ());
TEST_EQUAL(line[2], geometry::PointWithAltitude(mercator::FromLatLon(3, 3), geometry::kInvalidAltitude), ());
TEST_EQUAL(line[3], geometry::PointWithAltitude(mercator::FromLatLon(4, 4), 2), ());
TEST_EQUAL(line[4], geometry::PointWithAltitude(mercator::FromLatLon(5, 5), geometry::kInvalidAltitude), ());
TEST_EQUAL(line[5], geometry::PointWithAltitude(mercator::FromLatLon(6, 6), 3), ());
}
UNIT_TEST(Gpx_Timestamp_Issues)
{
std::string_view constexpr input = R"(<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.0">
<trk>
<name>new</name>
<type>Cycling</type>
<trkseg>
<trkpt lat="0" lon="0"></trkpt>
<trkpt lat="1" lon="1"><time>2024-05-04T19:00:00Z</time></trkpt>
<trkpt lat="2" lon="2"><time>2024-05-04T19:00:01Z</time></trkpt>
<trkpt lat="3" lon="3"></trkpt>
<trkpt lat="4" lon="4"><time>Abra-hadabra</time></trkpt>
<trkpt lat="5" lon="5"><time>2024-05-04T19:00:04Z</time></trkpt>
<trkpt lat="6" lon="6"><time>2024-05-04T19:00:05Z</time></trkpt>
<trkpt lat="7" lon="7"></trkpt>
</trkseg>
</trk>
</gpx>
)";
kml::FileData const dataFromText = LoadGpxFromString(input);
auto const & times = dataFromText.m_tracksData[0].m_geometry.m_timestamps[0];
TEST_EQUAL(times.size(), 8, ());
TEST_EQUAL(times[0], base::StringToTimestamp("2024-05-04T19:00:00Z"), ());
TEST_EQUAL(times[1], base::StringToTimestamp("2024-05-04T19:00:00Z"), ());
TEST_EQUAL(times[2], base::StringToTimestamp("2024-05-04T19:00:01Z"), ());
TEST_EQUAL(times[3], base::StringToTimestamp("2024-05-04T19:00:02Z"), ());
TEST_EQUAL(times[4], base::StringToTimestamp("2024-05-04T19:00:03Z"), ());
TEST_EQUAL(times[5], base::StringToTimestamp("2024-05-04T19:00:04Z"), ());
TEST_EQUAL(times[6], base::StringToTimestamp("2024-05-04T19:00:05Z"), ());
TEST_EQUAL(times[7], base::StringToTimestamp("2024-05-04T19:00:05Z"), ());
}
UNIT_TEST(GoMap)
{
kml::FileData const dataFromFile = LoadGpxFromFile("test_data/gpx/go_map.gpx");
auto const & line = dataFromFile.m_tracksData[0].m_geometry.m_lines[0];
TEST_EQUAL(line.size(), 101, ());
}
UNIT_TEST(GpxStudio)
{
kml::FileData const dataFromFile = LoadGpxFromFile("test_data/gpx/gpx_studio.gpx");
auto const & line = dataFromFile.m_tracksData[0].m_geometry.m_lines[0];
TEST_EQUAL(line.size(), 328, ());
}
UNIT_TEST(OsmTrack)
{
kml::FileData const dataFromFile = LoadGpxFromFile("test_data/gpx/osm_track.gpx");
auto const & line = dataFromFile.m_tracksData[0].m_geometry.m_lines[0];
auto const & timestamps = dataFromFile.m_tracksData[0].m_geometry.m_timestamps[0];
TEST_EQUAL(line.size(), 182, ());
TEST_EQUAL(timestamps.size(), 182, ());
}
UNIT_TEST(TowerCollector)
{
kml::FileData const dataFromFile = LoadGpxFromFile("test_data/gpx/tower_collector.gpx");
auto line = dataFromFile.m_tracksData[0].m_geometry.m_lines[0];
TEST_EQUAL(line.size(), 35, ());
}
UNIT_TEST(PointsOnly)
{
kml::FileData const dataFromFile = LoadGpxFromFile("test_data/gpx/points.gpx");
auto bookmarks = dataFromFile.m_bookmarksData;
TEST_EQUAL(bookmarks.size(), 3, ());
TEST_EQUAL("Point 1", bookmarks[0].m_name[kml::kDefaultLang], ());
TEST_EQUAL(bookmarks[0].m_point, mercator::FromLatLon(48.20984622935899, 16.376023292541507), ());
}
UNIT_TEST(Route)
{
kml::FileData dataFromFile = LoadGpxFromFile("test_data/gpx/route.gpx");
auto line = dataFromFile.m_tracksData[0].m_geometry.m_lines[0];
TEST_EQUAL(line.size(), 2, ());
TEST_EQUAL(dataFromFile.m_categoryData.m_name[kml::kDefaultLang], "Some random route", ());
TEST_EQUAL(line[0], geometry::PointWithAltitude(mercator::FromLatLon(48.20984622935899, 16.376023292541507), 184),
());
TEST_EQUAL(line[1], geometry::PointWithAltitude(mercator::FromLatLon(48.209503040543545, 16.381065845489506), 187),
());
}
UNIT_TEST(Color)
{
kml::FileData const dataFromFile = LoadGpxFromFile("test_data/gpx/color.gpx");
uint32_t const red = 0xFF0000FF;
uint32_t const blue = 0x0000FFFF;
uint32_t const black = 0x000000FF;
TEST_EQUAL(red, dataFromFile.m_tracksData[0].m_layers[0].m_color.m_rgba, ());
TEST_EQUAL(blue, dataFromFile.m_tracksData[1].m_layers[0].m_color.m_rgba, ());
TEST_EQUAL(black, dataFromFile.m_tracksData[2].m_layers[0].m_color.m_rgba, ());
TEST_EQUAL(dataFromFile.m_tracksData.size(), 3, ());
}
UNIT_TEST(ParseExportedGpxColor)
{
kml::FileData const dataFromFile = LoadGpxFromFile("test_data/gpx/point_with_predefined_color_2.gpx");
TEST_EQUAL(0x0066CCFF, dataFromFile.m_bookmarksData[0].m_color.m_rgba, ());
TEST_EQUAL(kml::PredefinedColor::Blue, dataFromFile.m_bookmarksData[0].m_color.m_predefinedColor, ());
}
UNIT_TEST(MultiTrackNames)
{
kml::FileData dataFromFile = LoadGpxFromFile("test_data/gpx/color.gpx");
TEST_EQUAL("new", dataFromFile.m_categoryData.m_name[kml::kDefaultLang], ());
TEST_EQUAL("Short description", dataFromFile.m_categoryData.m_description[kml::kDefaultLang], ());
TEST_EQUAL("new red", dataFromFile.m_tracksData[0].m_name[kml::kDefaultLang], ());
TEST_EQUAL("description 1", dataFromFile.m_tracksData[0].m_description[kml::kDefaultLang], ());
TEST_EQUAL("new blue", dataFromFile.m_tracksData[1].m_name[kml::kDefaultLang], ());
TEST_EQUAL("description 2", dataFromFile.m_tracksData[1].m_description[kml::kDefaultLang], ());
}
UNIT_TEST(Empty)
{
kml::FileData dataFromFile = LoadGpxFromFile("test_data/gpx/empty.gpx");
TEST_EQUAL("new", dataFromFile.m_categoryData.m_name[kml::kDefaultLang], ());
TEST_EQUAL(0, dataFromFile.m_tracksData.size(), ());
}
UNIT_TEST(ImportExportWptColor)
{
ImportExportCompare("test_data/gpx/point_with_predefined_color_2.gpx");
}
UNIT_TEST(PointWithPredefinedColor)
{
kml::FileData dataFromFile = LoadGpxFromFile("test_data/gpx/point_with_predefined_color_1.gpx");
dataFromFile.m_bookmarksData[0].m_color.m_predefinedColor = kml::PredefinedColor::Blue;
auto const actual = Serialize(dataFromFile);
auto const expected = ReadFile("test_data/gpx/point_with_predefined_color_2.gpx");
TEST_EQUAL(expected, actual, ());
}
UNIT_TEST(OsmandColor1)
{
kml::FileData const dataFromFile = LoadGpxFromFile("test_data/gpx/osmand1.gpx");
uint32_t constexpr expected = 0xFF7800FF;
TEST_EQUAL(dataFromFile.m_tracksData.size(), 4, ());
TEST_EQUAL(expected, dataFromFile.m_tracksData[0].m_layers[0].m_color.m_rgba, ());
TEST_EQUAL(expected, dataFromFile.m_tracksData[1].m_layers[0].m_color.m_rgba, ());
TEST_EQUAL(expected, dataFromFile.m_tracksData[2].m_layers[0].m_color.m_rgba, ());
TEST_EQUAL(expected, dataFromFile.m_tracksData[3].m_layers[0].m_color.m_rgba, ());
}
UNIT_TEST(OsmandColor2)
{
kml::FileData const dataFromFile = LoadGpxFromFile("test_data/gpx/osmand2.gpx");
uint32_t const expected1 = 0x00FF00FF;
uint32_t const expected2 = 0x1010A0FF;
TEST_EQUAL(expected1, dataFromFile.m_bookmarksData[0].m_color.m_rgba, ());
TEST_EQUAL(expected2, dataFromFile.m_bookmarksData[1].m_color.m_rgba, ());
}
UNIT_TEST(Gpx_Test_Cmt)
{
std::string const input = R"(<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.0">
<wpt lat="1" lon="2"><name>1</name><desc>d1</desc></wpt>
<wpt lat="1" lon="2"><name>2</name><desc>d2</desc><cmt>c2</cmt></wpt>
<wpt lat="1" lon="2"><name>3</name><cmt>c3</cmt></wpt>
<wpt lat="1" lon="2"><name>4</name>
<desc>
d4
d5
</desc>
<cmt>c4</cmt>
</wpt>
<wpt lat="1" lon="2"><name>5</name><cmt>qqq</cmt><desc>qqq</desc></wpt>
)";
kml::FileData const dataFromText = LoadGpxFromString(input);
TEST_EQUAL("d1", dataFromText.m_bookmarksData[0].m_description.at(kml::kDefaultLang), ());
TEST_EQUAL("d2\n\nc2", dataFromText.m_bookmarksData[1].m_description.at(kml::kDefaultLang), ());
TEST_EQUAL("c3", dataFromText.m_bookmarksData[2].m_description.at(kml::kDefaultLang), ());
TEST_EQUAL("d4\nd5\n\nc4", dataFromText.m_bookmarksData[3].m_description.at(kml::kDefaultLang), ());
TEST_EQUAL("qqq", dataFromText.m_bookmarksData[4].m_description.at(kml::kDefaultLang), ());
}
UNIT_TEST(OpentracksColor)
{
kml::FileData dataFromFile = LoadGpxFromFile("test_data/gpx/opentracks_color.gpx");
uint32_t const expected = 0xC0C0C0FF;
TEST_EQUAL(expected, dataFromFile.m_tracksData[0].m_layers[0].m_color.m_rgba, ());
}
UNIT_TEST(ParseFromString)
{
// String hex sequence #AARRGGBB, uint32 sequence RGBA
TEST_EQUAL(std::optional<uint32_t>(0x1FF), kml::gpx::GpxParser::ParseColorFromHexString("000001"), ());
TEST_EQUAL(std::optional<uint32_t>(0x100FF), kml::gpx::GpxParser::ParseColorFromHexString("000100"), ());
TEST_EQUAL(std::optional<uint32_t>(0x10000FF), kml::gpx::GpxParser::ParseColorFromHexString("010000"), ());
TEST_EQUAL(std::optional<uint32_t>(0x1FF), kml::gpx::GpxParser::ParseColorFromHexString("#000001"), ());
TEST_EQUAL(std::optional<uint32_t>(0x100FF), kml::gpx::GpxParser::ParseColorFromHexString("#000100"), ());
TEST_EQUAL(std::optional<uint32_t>(0x10000FF), kml::gpx::GpxParser::ParseColorFromHexString("#010000"), ());
TEST_EQUAL(std::optional<uint32_t>(0x1FF), kml::gpx::GpxParser::ParseColorFromHexString("#FF000001"), ());
TEST_EQUAL(std::optional<uint32_t>(0x100FF), kml::gpx::GpxParser::ParseColorFromHexString("#FF000100"), ());
TEST_EQUAL(std::optional<uint32_t>(0x10000FF), kml::gpx::GpxParser::ParseColorFromHexString("#FF010000"), ());
TEST_EQUAL(std::optional<uint32_t>(0x10000AA), kml::gpx::GpxParser::ParseColorFromHexString("#AA010000"), ());
TEST_EQUAL(std::optional<uint32_t>(), kml::gpx::GpxParser::ParseColorFromHexString("DarkRed"), ());
}
UNIT_TEST(MapGarminColor)
{
TEST_EQUAL("DarkCyan", kml::MapGarminColor(0x008b8bff), ());
TEST_EQUAL("White", kml::MapGarminColor(0xffffffff), ());
TEST_EQUAL("DarkYellow", kml::MapGarminColor(0xb4b820ff), ());
TEST_EQUAL("DarkYellow", kml::MapGarminColor(0xb6b820ff), ());
TEST_EQUAL("DarkYellow", kml::MapGarminColor(0xb5b721ff), ());
}
} // namespace gpx_tests

View file

@ -0,0 +1,159 @@
#include "testing/testing.hpp"
#include "kml/minzoom_quadtree.hpp"
#include "geometry/mercator.hpp"
#include "geometry/point2d.hpp"
#include <functional>
#include <limits>
#include <map>
#include <random>
#include <utility>
namespace
{
double constexpr kEps = std::numeric_limits<double>::epsilon();
m2::PointD MakeGlobalPoint(double x, double y)
{
m2::PointD point;
point.x = x;
point.y = y;
point.x *= mercator::Bounds::kRangeX;
point.y *= mercator::Bounds::kRangeY;
point.x += mercator::Bounds::kMinX;
point.y += mercator::Bounds::kMinY;
return point;
}
} // namespace
UNIT_TEST(Kml_MinzoomQuadtree_PopulationGrowthRate)
{
{
kml::MinZoomQuadtree<double /* rank */, std::less<double>> minZoomQuadtree{{} /* less */};
size_t const kMaxDepth = 5;
double const step = 1.0 / (1 << kMaxDepth);
for (int x = 0; x < (1 << kMaxDepth); ++x)
for (int y = 0; y < (1 << kMaxDepth); ++y)
minZoomQuadtree.Add(MakeGlobalPoint((0.5 + x) * step, (0.5 + y) * step), 0.0 /* rank */);
double const kCountPerTile = 1.0;
std::map<int /* zoom */, size_t> populationCount;
auto const incZoomPopulation = [&](double & rank, int zoom)
{
TEST_ALMOST_EQUAL_ABS(rank, 0.0, kEps, ());
++populationCount[zoom];
};
minZoomQuadtree.SetMinZoom(kCountPerTile, scales::GetUpperStyleScale(), incZoomPopulation);
TEST_EQUAL(populationCount.size(), kMaxDepth + 1, (populationCount));
auto p = populationCount.cbegin();
ASSERT(p != populationCount.cend(), ());
std::vector<size_t> partialSums;
partialSums.push_back(p->second);
while (++p != populationCount.cend())
partialSums.push_back(partialSums.back() + p->second);
TEST_EQUAL(partialSums.front(), 1, ());
TEST_EQUAL(partialSums.back(), (1 << (kMaxDepth * 2)), ());
auto const isGrowthExponential = [](size_t lhs, size_t rhs) { return rhs != 4 * lhs; };
TEST(std::adjacent_find(partialSums.cbegin(), partialSums.cend(), isGrowthExponential) == partialSums.cend(),
(partialSums));
}
std::mt19937 g(0);
auto const gen = [&g] { return std::generate_canonical<double, std::numeric_limits<uint32_t>::digits>(g); };
for (int i = 0; i < 5; ++i)
{
kml::MinZoomQuadtree<double /* rank */, std::less<double>> minZoomQuadtree{{} /* less */};
size_t const kTotalCount = 1 + g() % 10000;
for (size_t i = 0; i < kTotalCount; ++i)
{
auto const x = gen();
auto const y = gen();
auto const rank = gen();
minZoomQuadtree.Add(MakeGlobalPoint(x, y), rank);
}
double const kCountPerTile = 1.0 + kTotalCount * gen() / 2.0;
std::map<int /* zoom */, size_t> populationCount;
auto const incZoomPopulation = [&](double & /* rank */, int zoom) { ++populationCount[zoom]; };
minZoomQuadtree.SetMinZoom(kCountPerTile, scales::GetUpperStyleScale(), incZoomPopulation);
auto p = populationCount.cbegin();
ASSERT(p != populationCount.cend(), ());
std::vector<size_t> partialSums;
partialSums.push_back(p->second);
while (++p != populationCount.cend())
partialSums.push_back(partialSums.back() + p->second);
TEST_EQUAL(partialSums.back(), kTotalCount, ());
double areaScale = 1.0;
for (size_t i = 0; i < partialSums.size(); ++i)
{
auto const maxAbsErr = 4.0 * std::ceil(std::sqrt(kCountPerTile * areaScale)) + 4.0;
TEST_LESS_OR_EQUAL(partialSums[i], kCountPerTile * areaScale + maxAbsErr,
(kCountPerTile, maxAbsErr, partialSums));
areaScale *= 4.0;
}
}
}
UNIT_TEST(Kml_MinzoomQuadtree_CornerCases)
{
{
kml::MinZoomQuadtree<double /* rank */, std::less<double>> minZoomQuadtree{{} /* less */};
minZoomQuadtree.Add(MakeGlobalPoint(0.5, 0.5), 0.0 /* rank */);
double const kCountPerTile = 100.0;
auto const checkRank = [&](double & rank, int zoom)
{
TEST_ALMOST_EQUAL_ABS(rank, 0.0, kEps, ());
TEST_EQUAL(zoom, 1, ());
};
minZoomQuadtree.SetMinZoom(kCountPerTile, scales::GetUpperStyleScale(), checkRank);
}
{
kml::MinZoomQuadtree<double /* rank */, std::less<double>> minZoomQuadtree{{} /* less */};
double const kCountPerTile = 100.0;
auto const unreachable = [&](double & /* rank */, int /* zoom */) { TEST(false, ()); };
minZoomQuadtree.SetMinZoom(kCountPerTile, scales::GetUpperStyleScale(), unreachable);
}
}
UNIT_TEST(Kml_MinzoomQuadtree_MaxZoom)
{
kml::MinZoomQuadtree<double /* rank */, std::less<double>> minZoomQuadtree{{} /* less */};
size_t const kMaxDepth = 5;
double const step = 1.0 / (1 << kMaxDepth);
for (int x = 0; x < (1 << kMaxDepth); ++x)
for (int y = 0; y < (1 << kMaxDepth); ++y)
minZoomQuadtree.Add(MakeGlobalPoint((0.5 + x) * step, (0.5 + y) * step), 0.0 /* rank */);
double const kCountPerTile = 1.0;
std::map<int /* zoom */, size_t> populationCount;
auto const incZoomPopulation = [&](double & rank, int zoom)
{
TEST_ALMOST_EQUAL_ABS(rank, 0.0, kEps, ());
++populationCount[zoom];
};
int const kMaxZoom = 4;
minZoomQuadtree.SetMinZoom(kCountPerTile, kMaxZoom, incZoomPopulation);
TEST_EQUAL(populationCount.size(), kMaxZoom, (populationCount));
auto p = populationCount.cbegin();
ASSERT(p != populationCount.cend(), ());
std::vector<size_t> partialSums;
partialSums.push_back(p->second);
while (++p != populationCount.cend())
partialSums.push_back(partialSums.back() + p->second);
TEST_EQUAL(partialSums.front(), 1, ());
TEST_EQUAL(partialSums.back(), (1 << (kMaxDepth * 2)), ());
auto const isGrowthExponential = [](size_t lhs, size_t rhs) { return rhs != 4 * lhs; };
TEST(std::adjacent_find(partialSums.cbegin(), std::prev(partialSums.cend()), isGrowthExponential) ==
std::prev(partialSums.cend()),
(partialSums));
TEST_LESS_OR_EQUAL(4 * *std::prev(partialSums.cend(), 2), partialSums.back(), ());
}

View file

@ -0,0 +1,917 @@
#include "testing/testing.hpp"
#include "kml/kml_tests/tests_data.hpp"
#include "kml/serdes.hpp"
#include "kml/serdes_binary.hpp"
#include "map/bookmark_helpers.hpp"
#include "indexer/classificator_loader.hpp"
#include "platform/platform.hpp"
#include "coding/file_reader.hpp"
#include "coding/file_writer.hpp"
#include "coding/hex.hpp"
#include "coding/reader.hpp"
#include "coding/string_utf8_multilang.hpp"
#include "coding/writer.hpp"
#include "base/file_name_utils.hpp"
#include "base/scope_guard.hpp"
#include <cstring>
#include <functional>
#include <sstream>
#include <vector>
namespace
{
// This function can be used to generate textual representation of vector<uint8_t> like you see above.
std::string FormatBytesFromBuffer(std::vector<uint8_t> const & buffer)
{
std::stringstream ss;
for (size_t i = 1; i <= buffer.size(); i++)
{
ss << "0x" << NumToHex(buffer[i - 1]) << ", ";
if (i % 16 == 0)
ss << "\n";
}
return ss.str();
}
auto const kDefaultLang = StringUtf8Multilang::kDefaultCode;
auto const kEnLang = StringUtf8Multilang::kEnglishCode;
auto const kRuLang = static_cast<int8_t>(8);
kml::FileData GenerateKmlFileData()
{
kml::FileData result;
result.m_deviceId = "AAAA";
result.m_serverId = "AAAA-BBBB-CCCC-DDDD";
result.m_categoryData.m_name[kDefaultLang] = "Test category";
result.m_categoryData.m_name[kRuLang] = "Тестовая категория";
result.m_categoryData.m_description[kDefaultLang] = "Test description";
result.m_categoryData.m_description[kRuLang] = "Тестовое описание";
result.m_categoryData.m_annotation[kDefaultLang] = "Test annotation";
result.m_categoryData.m_annotation[kEnLang] = "Test annotation";
result.m_categoryData.m_imageUrl = "https://localhost/123.png";
result.m_categoryData.m_visible = true;
result.m_categoryData.m_authorName = "author";
result.m_categoryData.m_authorId = "12345";
result.m_categoryData.m_rating = 8.9;
result.m_categoryData.m_reviewsNumber = 567;
result.m_categoryData.m_lastModified = kml::TimestampClock::from_time_t(1000);
result.m_categoryData.m_accessRules = kml::AccessRules::Public;
result.m_categoryData.m_tags = {"mountains", "ski", "snowboard"};
result.m_categoryData.m_toponyms = {"12345", "54321"};
result.m_categoryData.m_languageCodes = {1, 2, 8};
result.m_categoryData.m_properties = {{"property1", "value1"}, {"property2", "value2"}};
kml::BookmarkData bookmarkData;
bookmarkData.m_name[kDefaultLang] = "Test bookmark";
bookmarkData.m_name[kRuLang] = "Тестовая метка";
bookmarkData.m_description[kDefaultLang] = "Test bookmark description";
bookmarkData.m_description[kRuLang] = "Тестовое описание метки";
bookmarkData.m_featureTypes = {718, 715};
bookmarkData.m_customName[kDefaultLang] = "Мое любимое место";
bookmarkData.m_customName[kEnLang] = "My favorite place";
bookmarkData.m_color = {kml::PredefinedColor::Blue, 0};
bookmarkData.m_icon = kml::BookmarkIcon::None;
bookmarkData.m_viewportScale = 15;
bookmarkData.m_timestamp = kml::TimestampClock::from_time_t(800);
bookmarkData.m_point = m2::PointD(45.9242, 56.8679);
bookmarkData.m_boundTracks = {0};
bookmarkData.m_visible = false;
bookmarkData.m_nearestToponym = "12345";
bookmarkData.m_minZoom = 10;
bookmarkData.m_properties = {{"bm_property1", "value1"}, {"bm_property2", "value2"}, {"score", "5"}};
bookmarkData.m_compilations = {1, 2, 3, 4, 5};
result.m_bookmarksData.emplace_back(std::move(bookmarkData));
kml::TrackData trackData;
trackData.m_localId = 0;
trackData.m_name[kDefaultLang] = "Test track";
trackData.m_name[kRuLang] = "Тестовый трек";
trackData.m_description[kDefaultLang] = "Test track description";
trackData.m_description[kRuLang] = "Тестовое описание трека";
trackData.m_layers = {{6.0, {kml::PredefinedColor::None, 0xff0000ff}},
{7.0, {kml::PredefinedColor::None, 0x00ff00ff}}};
trackData.m_timestamp = kml::TimestampClock::from_time_t(900);
trackData.m_geometry.AddLine({{{45.9242, 56.8679}, 1}, {{45.2244, 56.2786}, 2}, {{45.1964, 56.9832}, 3}});
trackData.m_visible = false;
trackData.m_nearestToponyms = {"12345", "54321", "98765"};
trackData.m_properties = {{"tr_property1", "value1"}, {"tr_property2", "value2"}};
result.m_tracksData.emplace_back(std::move(trackData));
kml::CategoryData compilationData1;
compilationData1.m_compilationId = 1;
compilationData1.m_type = kml::CompilationType::Collection;
compilationData1.m_name[kDefaultLang] = "Test collection";
compilationData1.m_name[kRuLang] = "Тестовая коллекция";
compilationData1.m_description[kDefaultLang] = "Test collection description";
compilationData1.m_description[kRuLang] = "Тестовое описание коллекции";
compilationData1.m_annotation[kDefaultLang] = "Test collection annotation";
compilationData1.m_annotation[kEnLang] = "Test collection annotation";
compilationData1.m_imageUrl = "https://localhost/1234.png";
compilationData1.m_visible = true;
compilationData1.m_authorName = "author";
compilationData1.m_authorId = "54321";
compilationData1.m_rating = 5.9;
compilationData1.m_reviewsNumber = 333;
compilationData1.m_lastModified = kml::TimestampClock::from_time_t(999);
compilationData1.m_accessRules = kml::AccessRules::Public;
compilationData1.m_tags = {"mountains", "ski"};
compilationData1.m_toponyms = {"8", "9"};
compilationData1.m_languageCodes = {1, 2, 8};
compilationData1.m_properties = {{"property1", "value1"}, {"property2", "value2"}};
result.m_compilationsData.push_back(std::move(compilationData1));
kml::CategoryData compilationData2;
compilationData2.m_compilationId = 4;
compilationData2.m_type = kml::CompilationType::Category;
compilationData2.m_name[kDefaultLang] = "Test category";
compilationData2.m_name[kRuLang] = "Тестовая категория";
compilationData2.m_description[kDefaultLang] = "Test category description";
compilationData2.m_description[kRuLang] = "Тестовое описание категории";
compilationData2.m_annotation[kDefaultLang] = "Test category annotation";
compilationData2.m_annotation[kEnLang] = "Test category annotation";
compilationData2.m_imageUrl = "https://localhost/134.png";
compilationData2.m_visible = false;
compilationData2.m_authorName = "author";
compilationData2.m_authorId = "11111";
compilationData2.m_rating = 3.3;
compilationData2.m_reviewsNumber = 222;
compilationData2.m_lastModified = kml::TimestampClock::from_time_t(323);
compilationData2.m_accessRules = kml::AccessRules::Public;
compilationData2.m_tags = {"mountains", "bike"};
compilationData2.m_toponyms = {"10", "11"};
compilationData2.m_languageCodes = {1, 2, 8};
compilationData2.m_properties = {{"property1", "value1"}, {"property2", "value2"}};
result.m_compilationsData.push_back(std::move(compilationData2));
return result;
}
kml::FileData GenerateKmlFileDataForTrackWithoutTimestamps()
{
auto data = GenerateKmlFileData();
auto & trackData = data.m_tracksData[0];
trackData.m_geometry.Clear();
trackData.m_geometry.AddLine({{{45.9242, 56.8679}, 1}, {{45.2244, 56.2786}, 2}, {{45.1964, 56.9832}, 3}});
trackData.m_geometry.AddTimestamps({});
return data;
}
kml::FileData GenerateKmlFileDataForTrackWithTimestamps()
{
auto data = GenerateKmlFileData();
auto & trackData = data.m_tracksData[0];
trackData.m_geometry.Clear();
// track 1 (without timestamps)
trackData.m_geometry.AddLine({{{45.9242, 56.8679}, 1}, {{45.2244, 56.2786}, 2}, {{45.1964, 56.9832}, 3}});
trackData.m_geometry.AddTimestamps({});
// track 2
trackData.m_geometry.AddLine({{{45.9242, 56.8679}, 1}, {{45.2244, 56.2786}, 2}, {{45.1964, 56.9832}, 3}});
trackData.m_geometry.AddTimestamps({0.0, 1.0, 2.0});
// track 3
trackData.m_geometry.AddLine({{{45.9242, 56.8679}, 1}, {{45.2244, 56.2786}, 2}});
trackData.m_geometry.AddTimestamps({0.0, 1.0});
return data;
}
} // namespace
// 1. Check text and binary deserialization from the prepared sources in memory.
UNIT_TEST(Kml_Deserialization_Text_Bin_Memory)
{
UNUSED_VALUE(FormatBytesFromBuffer({}));
kml::FileData dataFromText;
TEST_NO_THROW(
{
kml::DeserializerKml des(dataFromText);
MemReader reader(kTextKml, strlen(kTextKml));
des.Deserialize(reader);
},
());
// TODO: uncomment to output bytes to the log.
// std::vector<uint8_t> buffer;
// {
// kml::binary::SerializerKml ser(dataFromText);
// MemWriter<decltype(buffer)> sink(buffer);
// ser.Serialize(sink);
// }
// LOG(LINFO, (FormatBytesFromBuffer(buffer)));
kml::FileData dataFromBin;
TEST_NO_THROW(
{
MemReader reader(kBinKml.data(), kBinKml.size());
kml::binary::DeserializerKml des(dataFromBin);
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromText, dataFromBin, ());
}
// 2. Check text serialization to the memory blob and compare with prepared data.
UNIT_TEST(Kml_Serialization_Text_Memory)
{
kml::FileData data;
{
kml::DeserializerKml des(data);
MemReader reader(kTextKml, strlen(kTextKml));
des.Deserialize(reader);
}
std::string resultBuffer;
{
MemWriter<decltype(resultBuffer)> sink(resultBuffer);
kml::SerializerKml ser(data);
ser.Serialize(sink);
}
kml::FileData data2;
{
kml::DeserializerKml des(data2);
MemReader reader(resultBuffer.c_str(), resultBuffer.length());
des.Deserialize(reader);
}
TEST_EQUAL(data, data2, ());
}
// 3. Check binary serialization to the memory blob and compare with prepared data.
UNIT_TEST(Kml_Serialization_Bin_Memory)
{
kml::FileData data;
{
kml::binary::DeserializerKml des(data);
MemReader reader(kBinKml.data(), kBinKml.size());
des.Deserialize(reader);
}
std::vector<uint8_t> buffer;
{
kml::binary::SerializerKml ser(data);
MemWriter<decltype(buffer)> sink(buffer);
ser.Serialize(sink);
}
TEST_EQUAL(kBinKml, buffer, ());
kml::FileData data2;
{
kml::binary::DeserializerKml des(data2);
MemReader reader(buffer.data(), buffer.size());
des.Deserialize(reader);
}
TEST_EQUAL(data, data2, ());
}
// 4. Check deserialization from the text file.
UNIT_TEST(Kml_Deserialization_Text_File)
{
std::string const kmlFile = base::JoinPath(GetPlatform().TmpDir(), "tmp.kml");
SCOPE_GUARD(fileGuard, std::bind(&FileWriter::DeleteFileX, kmlFile));
TEST_NO_THROW(
{
FileWriter file(kmlFile);
file.Write(kTextKml, strlen(kTextKml));
},
());
kml::FileData dataFromFile;
TEST_NO_THROW(
{
kml::DeserializerKml des(dataFromFile);
FileReader reader(kmlFile);
des.Deserialize(reader);
},
());
kml::FileData dataFromText;
TEST_NO_THROW(
{
kml::DeserializerKml des(dataFromText);
MemReader reader(kTextKml, strlen(kTextKml));
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromFile, dataFromText, ());
}
// 5. Check deserialization from the binary file.
UNIT_TEST(Kml_Deserialization_Bin_File)
{
std::string const kmbFile = base::JoinPath(GetPlatform().TmpDir(), "tmp.kmb");
SCOPE_GUARD(fileGuard, std::bind(&FileWriter::DeleteFileX, kmbFile));
TEST_NO_THROW(
{
FileWriter file(kmbFile);
file.Write(kBinKml.data(), kBinKml.size());
},
());
kml::FileData dataFromFile;
TEST_NO_THROW(
{
kml::binary::DeserializerKml des(dataFromFile);
FileReader reader(kmbFile);
des.Deserialize(reader);
},
());
kml::FileData dataFromBin;
TEST_NO_THROW(
{
kml::binary::DeserializerKml des(dataFromBin);
MemReader reader(kBinKml.data(), kBinKml.size());
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromFile, dataFromBin, ());
}
// 6. Check serialization to the binary file. Here we use generated data.
// The data in RAM must be completely equal to the data in binary file.
UNIT_TEST(Kml_Serialization_Bin_File)
{
auto data = GenerateKmlFileData();
std::string const kmbFile = base::JoinPath(GetPlatform().TmpDir(), "tmp.kmb");
SCOPE_GUARD(fileGuard, std::bind(&FileWriter::DeleteFileX, kmbFile));
TEST_NO_THROW(
{
kml::binary::SerializerKml ser(data);
FileWriter writer(kmbFile);
ser.Serialize(writer);
},
());
kml::FileData dataFromFile;
TEST_NO_THROW(
{
kml::binary::DeserializerKml des(dataFromFile);
FileReader reader(kmbFile);
des.Deserialize(reader);
},
());
TEST_EQUAL(data, dataFromFile, ());
}
// 7. Check serialization to the text file. Here we use generated data.
// The text in the file can be not equal to the original generated data, because
// text representation does not support all features, e.g. we do not store ids for
// bookmarks and tracks.
UNIT_TEST(Kml_Serialization_Text_File_Track_Without_Timestamps)
{
classificator::Load();
auto data = GenerateKmlFileDataForTrackWithoutTimestamps();
std::string const kmlFile = base::JoinPath(GetPlatform().TmpDir(), "tmp.kml");
SCOPE_GUARD(fileGuard, std::bind(&FileWriter::DeleteFileX, kmlFile));
TEST_NO_THROW(
{
kml::SerializerKml ser(data);
FileWriter sink(kmlFile);
ser.Serialize(sink);
},
());
// TODO: uncomment to output KML to the log.
// std::string buffer;
// {
// kml::SerializerKml ser(data);
// MemWriter<decltype(buffer)> sink(buffer);
// ser.Serialize(sink);
// }
// LOG(LINFO, (buffer));
kml::FileData dataFromGeneratedFile;
TEST_NO_THROW(
{
kml::DeserializerKml des(dataFromGeneratedFile);
FileReader reader(kmlFile);
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromGeneratedFile, data, ());
kml::FileData dataFromFile;
TEST_NO_THROW(
{
kml::DeserializerKml des(dataFromFile);
FileReader reader(GetPlatform().TestsDataPathForFile("test_data/kml/generated.kml"));
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromFile, data, ());
std::string dataFromFileBuffer;
{
MemWriter<decltype(dataFromFileBuffer)> sink(dataFromFileBuffer);
kml::SerializerKml ser(dataFromFile);
ser.Serialize(sink);
}
std::string dataFromGeneratedFileBuffer;
{
MemWriter<decltype(dataFromGeneratedFileBuffer)> sink(dataFromGeneratedFileBuffer);
kml::SerializerKml ser(dataFromGeneratedFile);
ser.Serialize(sink);
}
TEST_EQUAL(dataFromFileBuffer, dataFromGeneratedFileBuffer, ());
}
UNIT_TEST(Kml_Serialization_Text_File_Tracks_With_Timestamps)
{
classificator::Load();
auto data = GenerateKmlFileDataForTrackWithTimestamps();
std::string const kmlFile = base::JoinPath(GetPlatform().TmpDir(), "tmp.kml");
SCOPE_GUARD(fileGuard, std::bind(&FileWriter::DeleteFileX, kmlFile));
TEST_NO_THROW(
{
kml::SerializerKml ser(data);
FileWriter sink(kmlFile);
ser.Serialize(sink);
},
());
kml::FileData dataFromGeneratedFile;
TEST_NO_THROW(
{
kml::DeserializerKml des(dataFromGeneratedFile);
FileReader reader(kmlFile);
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromGeneratedFile, data, ());
kml::FileData dataFromFile;
TEST_NO_THROW(
{
kml::DeserializerKml des(dataFromFile);
FileReader reader(GetPlatform().TestsDataPathForFile("test_data/kml/generated_mixed_tracks.kml"));
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromFile, data, ());
}
// 8. Check binary deserialization of v.3 format.
UNIT_TEST(Kml_Deserialization_From_Bin_V3_And_V4)
{
kml::FileData dataFromBinV3;
TEST_NO_THROW(
{
MemReader reader(kBinKmlV3.data(), kBinKmlV3.size());
kml::binary::DeserializerKml des(dataFromBinV3);
des.Deserialize(reader);
},
());
kml::FileData dataFromBinV4;
TEST_NO_THROW(
{
MemReader reader(kBinKmlV4.data(), kBinKmlV4.size());
kml::binary::DeserializerKml des(dataFromBinV4);
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromBinV3, dataFromBinV4, ());
}
UNIT_TEST(Kml_Deserialization_From_Bin_V6_And_V7)
{
kml::FileData dataFromBinV6;
TEST_NO_THROW(
{
MemReader reader(kBinKmlV6.data(), kBinKmlV6.size());
kml::binary::DeserializerKml des(dataFromBinV6);
des.Deserialize(reader);
},
());
kml::FileData dataFromBinV7;
TEST_NO_THROW(
{
MemReader reader(kBinKmlV7.data(), kBinKmlV7.size());
kml::binary::DeserializerKml des(dataFromBinV7);
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromBinV6, dataFromBinV7, ());
}
UNIT_TEST(Kml_Deserialization_From_Bin_V7_And_V8)
{
kml::FileData dataFromBinV7;
TEST_NO_THROW(
{
MemReader reader(kBinKmlV7.data(), kBinKmlV7.size());
kml::binary::DeserializerKml des(dataFromBinV7);
des.Deserialize(reader);
},
());
kml::FileData dataFromBinV8;
TEST_NO_THROW(
{
MemReader reader(kBinKmlV8.data(), kBinKmlV8.size());
kml::binary::DeserializerKml des(dataFromBinV8);
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromBinV7, dataFromBinV8, ());
}
UNIT_TEST(Kml_Deserialization_From_Bin_V8_And_V8MM)
{
kml::FileData dataFromBinV8;
TEST_NO_THROW(
{
MemReader reader(kBinKmlV8.data(), kBinKmlV8.size());
kml::binary::DeserializerKml des(dataFromBinV8);
des.Deserialize(reader);
},
());
kml::FileData dataFromBinV8MM;
TEST_NO_THROW(
{
MemReader reader(kBinKmlV8MM.data(), kBinKmlV8MM.size());
kml::binary::DeserializerKml des(dataFromBinV8MM);
des.Deserialize(reader);
},
());
// Can't compare dataFromBinV8.m_categoryData and dataFromBinV8MM.m_categoryData directly
// because new format has less properties and different m_id. Compare some properties here:
TEST_EQUAL(dataFromBinV8.m_categoryData.m_name, dataFromBinV8MM.m_categoryData.m_name, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_description, dataFromBinV8MM.m_categoryData.m_description, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_annotation, dataFromBinV8MM.m_categoryData.m_annotation, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_accessRules, dataFromBinV8MM.m_categoryData.m_accessRules, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_visible, dataFromBinV8MM.m_categoryData.m_visible, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_rating, dataFromBinV8MM.m_categoryData.m_rating, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_reviewsNumber, dataFromBinV8MM.m_categoryData.m_reviewsNumber, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_tags, dataFromBinV8MM.m_categoryData.m_tags, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_properties, dataFromBinV8MM.m_categoryData.m_properties, ());
TEST_EQUAL(dataFromBinV8.m_bookmarksData, dataFromBinV8MM.m_bookmarksData, ());
TEST_EQUAL(dataFromBinV8.m_tracksData, dataFromBinV8MM.m_tracksData, ());
}
UNIT_TEST(Kml_Deserialization_From_KMB_V8_And_V9MM)
{
kml::FileData dataFromBinV8;
TEST_NO_THROW(
{
MemReader reader(kBinKmlV8.data(), kBinKmlV8.size());
kml::binary::DeserializerKml des(dataFromBinV8);
des.Deserialize(reader);
},
());
kml::FileData dataFromBinV9MM;
TEST_NO_THROW(
{
MemReader reader(kBinKmlV9MM.data(), kBinKmlV9MM.size());
kml::binary::DeserializerKml des(dataFromBinV9MM);
des.Deserialize(reader);
},
());
// Can't compare dataFromBinV8.m_categoryData and dataFromBinV9MM.m_categoryData directly
// because new format has less properties and different m_id. Compare some properties here:
TEST_EQUAL(dataFromBinV8.m_categoryData.m_name, dataFromBinV9MM.m_categoryData.m_name, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_description, dataFromBinV9MM.m_categoryData.m_description, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_annotation, dataFromBinV9MM.m_categoryData.m_annotation, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_accessRules, dataFromBinV9MM.m_categoryData.m_accessRules, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_visible, dataFromBinV9MM.m_categoryData.m_visible, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_rating, dataFromBinV9MM.m_categoryData.m_rating, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_reviewsNumber, dataFromBinV9MM.m_categoryData.m_reviewsNumber, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_tags, dataFromBinV9MM.m_categoryData.m_tags, ());
TEST_EQUAL(dataFromBinV8.m_categoryData.m_properties, dataFromBinV9MM.m_categoryData.m_properties, ());
dataFromBinV8.m_bookmarksData[0].m_id =
dataFromBinV9MM.m_bookmarksData[0].m_id; // V8 and V9MM bookmarks have different IDs. Fix ID value manually.
TEST_EQUAL(dataFromBinV8.m_bookmarksData, dataFromBinV9MM.m_bookmarksData, ());
dataFromBinV8.m_tracksData[0].m_id =
dataFromBinV9MM.m_tracksData[0].m_id; // V8 and V9MM tracks have different IDs. Fix ID value manually.
TEST_EQUAL(dataFromBinV8.m_tracksData, dataFromBinV9MM.m_tracksData, ());
}
UNIT_TEST(Kml_Deserialization_From_KMB_V9MM_With_MultiGeometry)
{
kml::FileData dataFromBinV9MM;
TEST_NO_THROW(
{
MemReader reader(kBinKmlMultiGeometryV9MM.data(), kBinKmlMultiGeometryV9MM.size());
kml::binary::DeserializerKml des(dataFromBinV9MM);
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromBinV9MM.m_tracksData.size(), 1, ());
// Verify that geometry has two lines
auto lines = dataFromBinV9MM.m_tracksData[0].m_geometry.m_lines;
TEST_EQUAL(lines.size(), 2, ());
// Verify that each line has 3 points
auto line1 = lines[0];
auto line2 = lines[1];
TEST_EQUAL(line1.size(), 3, ());
TEST_EQUAL(line2.size(), 3, ());
}
UNIT_TEST(Kml_Ver_2_3)
{
std::string_view constexpr data = R"(<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" version="2.3">
<Placemark id="PM005">
<Track>
<when>2010-05-28T02:02:09Z</when>
<when>2010-05-28T02:02:35Z</when>
<when>2010-05-28T02:02:44Z</when>
<when>2010-05-28T02:02:53Z</when>
<when>2010-05-28T02:02:54Z</when>
<when>2010-05-28T02:02:55Z</when>
<when>2010-05-28T02:02:56Z</when>
<coord>-122.207881 37.371915 156.000000</coord>
<coord>-122.205712 37.373288 152.000000</coord>
<coord>-122.204678 37.373939 147.000000</coord>
<coord>-122.203572 37.374630 142.199997</coord>
<coord>-122.203451 37.374706 141.800003</coord>
<coord>-122.203329 37.374780 141.199997</coord>
<coord>-122.203207 37.374857 140.199997</coord>
</Track>
<gx:MultiTrack>
<altitudeMode>absolute</altitudeMode>
<gx:interpolate>0</gx:interpolate>
<gx:Track>
<gx:coord>9.42666332 52.94270656 95</gx:coord>
<when>2022-12-25T13:12:01.914Z</when>
<gx:coord>9.42682572 52.94270115 94</gx:coord>
<when>2022-12-25T13:12:36Z</when>
<gx:coord>9.42699411 52.94269624 94</gx:coord>
<when>2022-12-25T13:12:38Z</when>
<gx:coord>9.42716915 52.94268793 95</gx:coord>
<when>2022-12-25T13:12:40Z</when>
<gx:coord>9.42736231 52.94266046 95</gx:coord>
<when>2022-12-25T13:12:42Z</when>
<gx:coord>9.42757536 52.94266963 96</gx:coord>
<when>2022-12-25T13:12:44Z</when>
<ExtendedData>
<SchemaData schemaUrl="#geotrackerTrackSchema">
<gx:SimpleArrayData name="speed">
<gx:value>0</gx:value>
<gx:value>3.71</gx:value>
<gx:value>5.22</gx:value>
<gx:value>6.16</gx:value>
<gx:value>7.1</gx:value>
<gx:value>7.28</gx:value>
</gx:SimpleArrayData>
<gx:SimpleArrayData name="course">
<gx:value />
<gx:value>1.57</gx:value>
<gx:value>1.62</gx:value>
<gx:value>1.64</gx:value>
<gx:value>1.69</gx:value>
<gx:value>1.56</gx:value>
</gx:SimpleArrayData>
</SchemaData>
</ExtendedData>
</gx:Track>
</gx:MultiTrack>
</Placemark>
</kml>)";
kml::FileData fData;
TEST_NO_THROW({ kml::DeserializerKml(fData).Deserialize(MemReader(data)); }, ());
TEST_EQUAL(fData.m_tracksData.size(), 1, ());
auto const & geom = fData.m_tracksData[0].m_geometry;
auto const & lines = geom.m_lines;
auto const & timestamps = geom.m_timestamps;
TEST_EQUAL(lines.size(), 2, ());
TEST_EQUAL(lines[0].size(), 7, ());
TEST_EQUAL(lines[1].size(), 6, ());
TEST(geom.HasTimestamps(), ());
TEST(geom.HasTimestampsFor(0), ());
TEST(geom.HasTimestampsFor(1), ());
TEST_EQUAL(timestamps.size(), 2, ());
TEST_EQUAL(timestamps[0].size(), 7, ());
TEST_EQUAL(timestamps[1].size(), 6, ());
}
UNIT_TEST(Kml_Placemark_contains_both_Bookmark_and_Track_data)
{
std::string_view constexpr input = R"(<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Placemark>
<MultiGeometry>
<Point>
<coordinates>28.968447783842,41.009030507129,0</coordinates>
</Point>
<LineString>
<coordinates>28.968447783842,41.009030507129,0 28.965858,41.018449,0</coordinates>
</LineString>
</MultiGeometry>
</Placemark>
<Placemark>
<MultiGeometry>
<LineString>
<coordinates>28.968447783842,41.009030507129,0 28.965858,41.018449,0</coordinates>
</LineString>
<Point>
<coordinates>28.968447783842,41.009030507129,0</coordinates>
</Point>
</MultiGeometry>
</Placemark>
</kml>)";
kml::FileData fData;
TEST_NO_THROW({ kml::DeserializerKml(fData).Deserialize(MemReader(input)); }, ());
TEST_EQUAL(fData.m_bookmarksData.size(), 2, ());
TEST_EQUAL(fData.m_tracksData.size(), 2, ());
TEST(!fData.m_tracksData[0].m_geometry.HasTimestamps(), ());
TEST(!fData.m_tracksData[1].m_geometry.HasTimestamps(), ());
}
// See https://github.com/organicmaps/organicmaps/issues/5800
UNIT_TEST(Fix_Invisible_Color_Bug_In_Gpx_Tracks)
{
std::string_view constexpr input = R"(<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.2">
<Document>
<name>2023-08-20 Malente Radtour</name>
<visibility>1</visibility>
<Placemark>
<name>2023-08-20 Malente Radtour</name>
<Style><LineStyle>
<color>01000000</color>
<width>3</width>
</LineStyle></Style>
<LineString><coordinates>10.565979,54.16597,26 10.565956,54.165997,26</coordinates></LineString>
</Placemark>
<Placemark>
<name>Test default colors and width</name>
<LineString><coordinates>10.465979,54.16597,26 10.465956,54.165997,26</coordinates></LineString>
</Placemark>
</Document>
</kml>)";
kml::FileData fData;
TEST_NO_THROW({ kml::DeserializerKml(fData).Deserialize(MemReader(input)); }, ());
TEST_EQUAL(fData.m_tracksData.size(), 2, ());
TEST_EQUAL(fData.m_tracksData[0].m_layers.size(), 1, ());
auto const & layer = fData.m_tracksData[0].m_layers[0];
TEST_EQUAL(layer.m_color.m_rgba, kml::kDefaultTrackColor, ("Wrong transparency should be fixed"));
TEST_EQUAL(layer.m_lineWidth, 3, ());
}
UNIT_TEST(Kml_Tracks_With_Different_Points_And_Timestamps_Order)
{
kml::FileData dataFromFile;
TEST_NO_THROW(
{
kml::DeserializerKml des(dataFromFile);
FileReader reader(
GetPlatform().TestsDataPathForFile("test_data/kml/track_with_timestams_different_orders.kml"));
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromFile.m_tracksData.size(), 1, ());
auto const & geom = dataFromFile.m_tracksData[0].m_geometry;
TEST_EQUAL(geom.m_lines.size(), 4, ());
TEST_EQUAL(geom.m_timestamps.size(), 4, ());
TEST_EQUAL(geom.m_lines[0], geom.m_lines[1], ());
TEST_EQUAL(geom.m_lines[0], geom.m_lines[2], ());
TEST_EQUAL(geom.m_lines[0], geom.m_lines[3], ());
TEST_EQUAL(geom.m_timestamps[0], geom.m_timestamps[1], ());
TEST_EQUAL(geom.m_timestamps[0], geom.m_timestamps[2], ());
TEST_EQUAL(geom.m_timestamps[0], geom.m_timestamps[3], ());
}
UNIT_TEST(Kml_Track_Points_And_Timestamps_Sizes_Mismatch)
{
kml::FileData dataFromFile;
TEST_ANY_THROW(
{
kml::DeserializerKml des(dataFromFile);
FileReader reader(GetPlatform().TestsDataPathForFile("test_data/kml/track_with_timestamps_mismatch.kml"));
des.Deserialize(reader);
},
());
TEST_EQUAL(dataFromFile.m_tracksData.size(), 0, ());
}
// https://github.com/organicmaps/organicmaps/issues/9290
UNIT_TEST(Kml_Import_OpenTracks)
{
std::string_view constexpr input = R"(<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.2">
<Placemark>
<Track>
<when>2010-05-28T02:00Z</when>
<when>2010-05-28T02:01Z</when>
<when>2010-05-28T02:02Z</when>
<when>2010-05-28T02:03Z</when>
<when>2010-05-28T02:04Z</when>
<coord/>
<coord>-122.205712 37.373288 152.000000</coord>
<coord>Abra-cadabra</coord>
<coord>-122.203572 37.374630 142.199997</coord>
<coord/>
</Track>
</Placemark>
</kml>)";
kml::FileData fData;
TEST_NO_THROW({ kml::DeserializerKml(fData).Deserialize(MemReader(input)); }, ());
{
TEST_EQUAL(fData.m_tracksData.size(), 1, ());
auto const & geom = fData.m_tracksData[0].m_geometry;
TEST_EQUAL(geom.m_lines.size(), 1, ());
TEST_EQUAL(geom.m_lines.size(), geom.m_timestamps.size(), ());
TEST_EQUAL(geom.m_lines[0].size(), 2, ());
TEST_EQUAL(geom.m_lines[0].size(), geom.m_timestamps[0].size(), ());
TEST_EQUAL(geom.m_timestamps[0][0], base::StringToTimestamp("2010-05-28T02:01Z"), ());
TEST_EQUAL(geom.m_timestamps[0][1], base::StringToTimestamp("2010-05-28T02:03Z"), ());
}
fData = {};
TEST_NO_THROW(
{
kml::DeserializerKml des(fData);
FileReader reader(GetPlatform().TestsDataPathForFile("test_data/kml/track_from_OpenTracks.kml"));
des.Deserialize(reader);
},
());
{
TEST_EQUAL(fData.m_tracksData.size(), 1, ());
auto const & geom = fData.m_tracksData[0].m_geometry;
TEST_EQUAL(geom.m_lines.size(), 1, ());
TEST_EQUAL(geom.m_lines.size(), geom.m_timestamps.size(), ());
TEST_GREATER(geom.m_lines[0].size(), 10, ());
}
}
UNIT_TEST(Kml_BadTracks)
{
std::string_view constexpr input = R"(<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.2">
<Placemark>
<Track>
<when>2010-05-28T02:00Z</when>
<coord>-122.205712 37.373288 152.000000</coord>
</Track>
<gx:Track>
<gx:coord>9.42666332 52.94270656 95</gx:coord>
<when>2022-12-25T13:12:01.914Z</when>
</gx:Track>
<gx:Track>
<gx:coord>9.42666332 52.94270656 95</gx:coord>
<when>2022-12-25T13:12:01.914Z</when>
<gx:coord>9.42682572 52.94270115 94</gx:coord>
<when>2022-12-25T13:12:36Z</when>
</gx:Track>
</Placemark>
</kml>)";
kml::FileData fData;
TEST_NO_THROW({ kml::DeserializerKml(fData).Deserialize(MemReader(input)); }, ());
{
TEST_EQUAL(fData.m_tracksData.size(), 1, ());
auto const & geom = fData.m_tracksData[0].m_geometry;
TEST_EQUAL(geom.m_lines.size(), 1, ());
TEST_EQUAL(geom.m_lines.size(), geom.m_timestamps.size(), ());
TEST_EQUAL(geom.m_lines[0].size(), 2, ());
TEST_EQUAL(geom.m_lines[0].size(), geom.m_timestamps[0].size(), ());
}
}

File diff suppressed because it is too large Load diff