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

40
libs/kml/CMakeLists.txt Normal file
View file

@ -0,0 +1,40 @@
project(kml)
set(SRC
color_parser.cpp
color_parser.hpp
header_binary.hpp
minzoom_quadtree.hpp
serdes_common.cpp
serdes_common.hpp
serdes.cpp
serdes.hpp
serdes_binary.cpp
serdes_binary.hpp
serdes_binary_v8.hpp
serdes_gpx.cpp
serdes_gpx.hpp
type_utils.cpp
type_utils.hpp
types.cpp
types.hpp
types_v3.hpp
types_v6.hpp
types_v7.hpp
types_v8.hpp
types_v8mm.hpp
types_v9.hpp
types_v9mm.hpp
visitors.hpp
)
omim_add_library(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME} indexer)
omim_add_test_subdirectory(kml_tests)
if(PLATFORM_DESKTOP)
omim_add_tool_subdirectory(kmb_to_kml)
omim_add_tool_subdirectory(kml_to_kmb)
endif()

214
libs/kml/color_parser.cpp Normal file
View file

@ -0,0 +1,214 @@
#include "color_parser.hpp"
#include "coding/hex.hpp"
#include "types.hpp"
#include "base/string_utils.hpp"
namespace kml
{
struct RGBColor
{
uint8_t r, g, b;
};
std::optional<uint32_t> ParseHexColor(std::string_view c)
{
if (c.empty())
return {};
if (c.front() == '#')
c.remove_prefix(1);
if (c.size() != 6 && c.size() != 8)
return {};
auto const colorBytes = FromHex(c);
switch (colorBytes.size())
{
case 3: return ToRGBA(colorBytes[0], colorBytes[1], colorBytes[2]);
case 4: return ToRGBA(colorBytes[1], colorBytes[2], colorBytes[3], colorBytes[0]);
default: return {};
}
}
std::tuple<int, int, int> ExtractRGB(uint32_t rgbaColor)
{
return {(rgbaColor >> 24) & 0xFF, (rgbaColor >> 16) & 0xFF, (rgbaColor >> 8) & 0xFF};
}
static int ColorDistance(uint32_t rgbaColor1, uint32_t rgbaColor2)
{
auto const [r1, g1, b1] = ExtractRGB(rgbaColor1);
auto const [r2, g2, b2] = ExtractRGB(rgbaColor2);
return (r1 - r2) * (r1 - r2) + (g1 - g2) * (g1 - g2) + (b1 - b2) * (b1 - b2);
}
struct RGBAToGarmin
{
uint32_t rgba;
std::string_view color;
};
auto constexpr kRGBAToGarmin = std::to_array<RGBAToGarmin>({{0x000000ff, "Black"},
{0x8b0000ff, "DarkRed"},
{0x006400ff, "DarkGreen"},
{0xb5b820ff, "DarkYellow"},
{0x00008bff, "DarkBlue"},
{0x8b008bff, "DarkMagenta"},
{0x008b8bff, "DarkCyan"},
{0xccccccff, "LightGray"},
{0x444444ff, "DarkGray"},
{0xff0000ff, "Red"},
{0x00ff00ff, "Green"},
{0xffff00ff, "Yellow"},
{0x0000ffff, "Blue"},
{0xff00ffff, "Magenta"},
{0x00ffffff, "Cyan"},
{0xffffffff, "White"}});
std::string_view MapGarminColor(uint32_t rgba)
{
std::string_view closestColor = kRGBAToGarmin[0].color;
auto minDistance = std::numeric_limits<int>::max();
for (auto const & [rgbaGarmin, color] : kRGBAToGarmin)
{
auto const distance = ColorDistance(rgba, rgbaGarmin);
if (distance == 0)
return color; // Exact match.
if (distance < minDistance)
{
minDistance = distance;
closestColor = color;
}
}
return closestColor;
}
struct RGBAToPredefined
{
uint32_t rgba;
PredefinedColor predefinedColor;
};
static std::array<RGBAToPredefined, kOrderedPredefinedColors.size()> buildRGBAToPredefined()
{
auto res = std::array<RGBAToPredefined, kOrderedPredefinedColors.size()>();
for (size_t i = 0; i < kOrderedPredefinedColors.size(); ++i)
res[i] = {ColorFromPredefinedColor(kOrderedPredefinedColors[i]).GetRGBA(), kOrderedPredefinedColors[i]};
return res;
}
auto const kRGBAToPredefined = buildRGBAToPredefined();
PredefinedColor MapPredefinedColor(uint32_t rgba)
{
auto closestColor = kRGBAToPredefined[0].predefinedColor;
auto minDistance = std::numeric_limits<int>::max();
for (auto const & [rgbaGarmin, color] : kRGBAToPredefined)
{
auto const distance = ColorDistance(rgba, rgbaGarmin);
if (distance == 0)
return color; // Exact match.
if (distance < minDistance)
{
minDistance = distance;
closestColor = color;
}
}
return closestColor;
}
// Garmin extensions spec: https://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd
// Color mapping: https://help.locusmap.eu/topic/extend-garmin-gpx-compatibilty
std::optional<uint32_t> ParseGarminColor(std::string_view c)
{
/// @todo Unify with RGBColor instead of string.
static std::pair<std::string_view, std::string_view> arrColors[] = {
{"Black", "000000"}, {"DarkRed", "8b0000"}, {"DarkGreen", "006400"}, {"DarkYellow", "b5b820"},
{"DarkBlue", "00008b"}, {"DarkMagenta", "8b008b"}, {"DarkCyan", "008b8b"}, {"LightGray", "cccccc"},
{"DarkGray", "444444"}, {"Red", "ff0000"}, {"Green", "00ff00"}, {"Yellow", "ffff00"},
{"Blue", "0000ff"}, {"Magenta", "ff00ff"}, {"Cyan", "00ffff"}, {"White", "ffffff"}};
for (auto const & e : arrColors)
if (c == e.first)
return ParseHexColor(e.second);
return {};
}
std::optional<uint32_t> ParseOSMColor(std::string_view c)
{
static std::pair<std::string_view, RGBColor> arrColors[] = {
{"black", {0, 0, 0}},
{"white", {255, 255, 255}},
{"red", {255, 0, 0}},
{"green", {0, 128, 0}},
{"blue", {0, 0, 255}},
{"yellow", {255, 255, 0}},
{"orange", {255, 165, 0}},
{"gray", {128, 128, 128}},
{"grey", {128, 128, 128}}, // British spelling
{"brown", {165, 42, 42}},
{"pink", {255, 192, 203}},
{"purple", {128, 0, 128}},
{"cyan", {0, 255, 255}},
{"magenta", {255, 0, 255}},
{"maroon", {128, 0, 0}},
{"olive", {128, 128, 0}},
{"teal", {0, 128, 128}},
{"navy", {0, 0, 128}},
{"silver", {192, 192, 192}},
{"lime", {0, 255, 0}},
{"aqua", {0, 255, 255}}, // cyan
{"fuchsia", {255, 0, 255}}, // magenta
// From top taginfo for "colour" and CSS standart values.
{"darkgreen", {0, 100, 0}},
{"beige", {245, 245, 220}},
{"dimgray", {105, 105, 105}},
{"lightgrey", {211, 211, 211}}, // British spelling
{"lightgray", {211, 211, 211}},
{"tan", {210, 180, 140}},
{"gold", {255, 215, 0}},
{"red;white", {255, 127, 127}},
{"red and white", {255, 127, 127}},
{"red-white", {255, 127, 127}},
};
if (!c.empty())
{
if (c[0] == '#')
{
using strings::to_uint;
if (c.size() == 7) // #rrggbb
{
uint8_t r, g, b;
if (to_uint(c.substr(1, 2), r, 16) && to_uint(c.substr(3, 2), g, 16) && to_uint(c.substr(5, 2), b, 16))
return ToRGBA(r, g, b);
}
else if (c.size() == 4) // #rgb shorthand
{
uint8_t r, g, b;
if (to_uint(c.substr(1, 1), r, 16) && to_uint(c.substr(2, 1), g, 16) && to_uint(c.substr(3, 1), b, 16))
return ToRGBA(uint8_t(r * 17), uint8_t(g * 17), uint8_t(b * 17));
}
}
else
{
for (auto const & e : arrColors)
if (c == e.first)
return ToRGBA(e.second.r, e.second.g, e.second.b);
}
}
return {};
}
} // namespace kml

26
libs/kml/color_parser.hpp Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <cstdint>
#include <optional>
#include <string_view>
#include "types.hpp"
namespace kml
{
template <typename Channel>
constexpr uint32_t ToRGBA(Channel red, Channel green, Channel blue, Channel alpha = Channel(255))
{
return static_cast<uint8_t>(red) << 24 | static_cast<uint8_t>(green) << 16 | static_cast<uint8_t>(blue) << 8 |
static_cast<uint8_t>(alpha);
}
std::optional<uint32_t> ParseHexColor(std::string_view c);
std::optional<uint32_t> ParseGarminColor(std::string_view c);
std::optional<uint32_t> ParseOSMColor(std::string_view c);
PredefinedColor MapPredefinedColor(uint32_t rgba);
std::string_view MapGarminColor(uint32_t rgba);
} // namespace kml

View file

@ -0,0 +1,89 @@
#pragma once
#include "coding/serdes_binary_header.hpp"
#include <cstdint>
namespace kml
{
namespace binary
{
enum class Version : uint8_t
{
V0 = 0,
V1 = 1, // 11th April 2018: new Point2D storage, added deviceId, feature name -> custom name.
V2 = 2, // 25th April 2018: added serverId.
V3 = 3, // 7th May 2018: persistent feature types. V3 is binary compatible with lower versions.
V4 = 4, // 26th August 2019: key-value properties and nearestToponym for bookmarks and tracks,
// cities -> toponyms.
V5 = 5, // 21st November 2019: extended color palette.
V6 = 6, // 3rd December 2019: extended bookmark icons. V6 is binary compatible with V4 and V5
// versions.
V7 = 7, // 13th February 2020: track points are replaced by points with altitude.
V8 = 8, // 24 September 2020: add compilations to types and corresponding section to kmb and
// tags to kml
V9 = 9, // 01 October 2020: add minZoom to bookmarks
Latest = V9,
V8MM = 10, // 27 July 2023: MapsMe released version v15.0.71617. Technically its version is 8
// (first byte is 0x08), but it's not compatible with V8 from this repo. It has
// no compilations.
V9MM = 11 // In July 2024 MapsMe released version with a new KMB format. Technically its version is 9
// (first byte is 0x09), but it's not compatible with OrganicMaps V9 from this repo.
// It supports multiline geometry.
};
inline std::string DebugPrint(Version v)
{
return ::DebugPrint(static_cast<int>(v));
}
struct Header
{
template <typename Visitor>
void Visit(Visitor & visitor)
{
visitor(m_categoryOffset, "categoryOffset");
visitor(m_bookmarksOffset, "bookmarksOffset");
visitor(m_tracksOffset, "tracksOffset");
if (HasCompilationsSection())
visitor(m_compilationsOffset, "compilationsOffset");
visitor(m_stringsOffset, "stringsOffset");
if (!HasCompilationsSection())
m_compilationsOffset = m_stringsOffset;
visitor(m_eosOffset, "eosOffset");
}
template <typename Sink>
void Serialize(Sink & sink)
{
coding::binary::HeaderSerVisitor<Sink> visitor(sink);
visitor(*this);
}
template <typename Source>
void Deserialize(Source & source)
{
coding::binary::HeaderDesVisitor<Source> visitor(source);
visitor(*this);
}
// Calculates the size of serialized header in bytes.
uint64_t Size()
{
coding::binary::HeaderSizeOfVisitor visitor;
visitor(*this);
return visitor.m_size;
}
bool HasCompilationsSection() const { return m_version == Version::V8 || m_version == Version::V9; }
Version m_version = Version::Latest;
uint64_t m_categoryOffset = 0;
uint64_t m_bookmarksOffset = 0;
uint64_t m_tracksOffset = 0;
uint64_t m_compilationsOffset = 0;
uint64_t m_stringsOffset = 0;
uint64_t m_eosOffset = 0;
};
} // namespace binary
} // namespace kml

View file

@ -0,0 +1,8 @@
# Console utility to convert KMB file to KML.
project(kmb_to_kml)
set(SRC kmb_to_kml.cpp)
omim_add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME} kml)

View file

@ -0,0 +1,60 @@
#include "kml/serdes.hpp"
#include "kml/serdes_binary.hpp"
#include "indexer/classificator_loader.hpp"
#include "coding/file_reader.hpp"
#include "coding/file_writer.hpp"
#include <iostream>
int main(int argc, char ** argv)
{
if (argc < 2)
{
std::cout << "Converts kmb file(s) to kml\n";
std::cout << "Usage: " << argv[0] << " path_to_kmb_file [path_to_another_kmb_file...]\n";
return 1;
}
// TODO: Why bookmarks serialization requires classifier?
classificator::Load();
do
{
std::string filePath = argv[argc - 1];
kml::FileData kmlData;
try
{
FileReader reader(filePath);
kml::binary::DeserializerKml des(kmlData);
des.Deserialize(reader);
}
catch (kml::binary::DeserializerKml::DeserializeException const & ex)
{
std::cerr << "Error reading kmb file " << filePath << ": " << ex.what() << std::endl;
return 1;
}
try
{
// Change extension to kml.
filePath[filePath.size() - 1] = 'l';
kml::SerializerKml ser(kmlData);
FileWriter kmlFile(filePath);
ser.Serialize(kmlFile);
}
catch (kml::SerializerKml::SerializeException const & ex)
{
std::cerr << "Error encoding to kml file " << filePath << ": " << ex.what() << std::endl;
return 1;
}
catch (FileWriter::Exception const & ex)
{
std::cerr << "Error writing to kml file " << filePath << ": " << ex.what() << std::endl;
return 1;
}
std::cout << "Saved converted file as " << filePath << std::endl;
}
while (--argc > 1);
return 0;
}

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

View file

@ -0,0 +1,8 @@
# Console utility to convert KML file to KMB.
project(kml_to_kmb)
set(SRC kml_to_kmb.cpp)
omim_add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME} kml)

View file

@ -0,0 +1,78 @@
#include "kml/serdes.hpp"
#include "kml/serdes_binary.hpp"
#include "kml/serdes_binary_v8.hpp"
#include "indexer/classificator_loader.hpp"
#include "coding/file_reader.hpp"
#include "coding/file_writer.hpp"
#include <iostream>
int main(int argc, char ** argv)
{
if (argc < 2)
{
std::cout << "Converts kml file to kmb\n";
std::cout << "Usage: " << argv[0] << " path_to_kml_file [KBM_FORMAT_VERSION]\n";
std::cout << "KBM_FORMAT_VERSION could be V8, V9, or Latest (default)\n";
return 1;
}
kml::binary::Version outVersion = kml::binary::Version::Latest;
if (argc >= 3)
{
std::string const versionStr = argv[2];
if (versionStr == "V8")
outVersion = kml::binary::Version::V8;
else if (versionStr != "V9" && versionStr != "Latest")
{
std::cout << "Invalid format version: " << versionStr << '\n';
return 2;
}
}
// TODO: Why bookmarks serialization requires classifier?
classificator::Load();
std::string filePath = argv[1];
kml::FileData kmlData;
try
{
FileReader reader(filePath);
kml::DeserializerKml des(kmlData);
des.Deserialize(reader);
}
catch (kml::DeserializerKml::DeserializeException const & ex)
{
std::cerr << "Error reading kml file " << filePath << ": " << ex.what() << std::endl;
return 1;
}
try
{
// Change extension to kmb.
filePath[filePath.size() - 1] = 'b';
if (outVersion == kml::binary::Version::V9)
{
kml::binary::SerializerKml ser(kmlData);
FileWriter kmlFile(filePath);
ser.Serialize(kmlFile);
}
else if (outVersion == kml::binary::Version::V8)
{
kml::binary::SerializerKmlV8 ser(kmlData);
FileWriter kmlFile(filePath);
ser.Serialize(kmlFile);
}
}
catch (kml::SerializerKml::SerializeException const & ex)
{
std::cerr << "Error encoding to kmb file " << filePath << ": " << ex.what() << std::endl;
return 1;
}
catch (FileWriter::Exception const & ex)
{
std::cerr << "Error writing to kmb file " << filePath << ": " << ex.what() << std::endl;
return 1;
}
return 0;
}

View file

@ -0,0 +1,171 @@
#pragma once
#include "kml/types.hpp"
#include "indexer/scales.hpp"
#include "geometry/mercator.hpp"
#include "geometry/rect2d.hpp"
#include "base/assert.hpp"
#include "base/bits.hpp"
#include <algorithm>
#include <cmath>
#include <iterator>
#include <limits>
#include <utility>
#include <vector>
namespace kml
{
// Builds linear quadtree of input values using coordinates provided
// then walks through the quadtree elevating minZoom of those values
// which are greater (in sense of Less) than others at the same
// quadtree level.
// To encode each quadtree level uses two bits. One from ordinate and
// another from abscissa. Both components are mapped to a coordinate
// system that uses the full range of uint32_t inside the bounding
// (squared) box.
template <typename Value, typename Less>
class MinZoomQuadtree
{
public:
MinZoomQuadtree(Less const & less) : m_less{less} {}
template <typename V>
void Add(m2::PointD const & point, V && value)
{
m_quadtree.emplace_back(point, std::forward<V>(value));
}
void Clear() { m_quadtree.clear(); }
template <typename F>
void SetMinZoom(double countPerTile, int maxZoom, F setMinZoom /* void (Value & value, int minZoom) */)
{
CHECK_GREATER(countPerTile, 0.0, ());
CHECK_LESS_OR_EQUAL(maxZoom, scales::GetUpperStyleScale(), ());
CHECK_GREATER_OR_EQUAL(maxZoom, 1, ());
if (m_quadtree.empty())
return;
m2::RectD bbox;
for (auto const & elem : m_quadtree)
bbox.Add(elem.m_point);
if (!(bbox.SizeX() > 0.0 || bbox.SizeY() > 0.0))
return;
// `spacing` is an characteristic interval between values on the map as if they spaced
// uniformly across the map providing required density (i.e. "count per tile area").
double spacing = std::min(mercator::Bounds::kRangeX / bbox.SizeX(), mercator::Bounds::kRangeY / bbox.SizeY());
spacing /= std::sqrt(countPerTile);
// `spacing` value decomposed into a normalized fraction and an integral power of two.
// Normalized fraction `scale` (in [1, 2)) used to slightly enlarge bounding box.
// Integral power of two `baseZoom` is biased exponent of inverse value. It has
// a meaning exactly the same as the other "zoomLevel"s in the project.
// Scaling (enlarging) the size of bbox by `scale` is required so that one can
// set arbitrary values for `countPerTile`, not only powers of four.
int baseZoom;
double const scale = 2.0 * std::frexp(spacing, &baseZoom);
auto const setMaxZoom = [&](auto const treeBeg, auto const treeEnd)
{
auto const topRanked = std::max_element(
treeBeg, treeEnd, [&](auto const & lhs, auto const & rhs) { return m_less(lhs.m_value, rhs.m_value); });
auto const setMaxZoom = [&](auto & elem) { setMinZoom(elem.m_value, maxZoom); };
std::for_each(treeBeg, topRanked, setMaxZoom);
std::for_each(std::next(topRanked), treeEnd, setMaxZoom);
return &*topRanked;
};
if (baseZoom >= maxZoom)
{
// At least one element is visible from lowest zoom level.
setMinZoom(setMaxZoom(m_quadtree.begin(), m_quadtree.end())->m_value, 1);
return;
}
double const size = std::max(bbox.SizeX(), bbox.SizeY()) * scale;
bbox.SetSizes(size, size);
// Calculate coordinate along z-order curve.
auto const [minX, minY] = bbox.LeftBottom();
double const gridScale = std::numeric_limits<uint32_t>::max() / size;
for (auto & elem : m_quadtree)
{
auto const [x, y] = elem.m_point;
uint32_t const gridX = static_cast<uint32_t>(std::trunc((x - minX) * gridScale));
uint32_t const gridY = static_cast<uint32_t>(std::trunc((y - minY) * gridScale));
elem.m_zCurveCoord = bits::BitwiseMerge(gridX, gridY);
}
std::sort(m_quadtree.begin(), m_quadtree.end());
int constexpr depthMax = std::numeric_limits<uint32_t>::digits;
uint64_t constexpr firstLevelMask = uint64_t{0b11} << ((depthMax - 1) * 2);
auto const traverse = [&](auto const & traverse, auto const treeBeg, auto const treeEnd, int depth) -> Element *
{
if (treeBeg == treeEnd)
return nullptr;
if (std::next(treeBeg) == treeEnd)
return &*treeBeg;
int const zoom = baseZoom + depth;
if (zoom >= maxZoom)
return setMaxZoom(treeBeg, treeEnd);
Element * topRanked = nullptr;
CHECK_LESS_OR_EQUAL(depth, depthMax, ("Quadtree is too deep, try to decrease maxZoom", maxZoom));
uint64_t const treeLevelMask = firstLevelMask >> ((depth - 1) * 2);
uint64_t const quadIncrement = treeLevelMask & (treeLevelMask >> 1);
uint64_t quadIndex = 0;
auto const quadMaskEqual = [&](auto const & elem) -> bool
{ return (elem.m_zCurveCoord & treeLevelMask) == quadIndex; };
auto quadBeg = treeBeg;
for (int quadrant = 0; quadrant < 4; ++quadrant)
{
ASSERT(std::is_partitioned(quadBeg, treeEnd, quadMaskEqual), ());
auto const quadEnd = std::partition_point(quadBeg, treeEnd, quadMaskEqual);
if (auto const elem = traverse(traverse, quadBeg, quadEnd, depth + 1))
{
if (topRanked == nullptr)
topRanked = elem;
else if (m_less(topRanked->m_value, elem->m_value))
setMinZoom(std::exchange(topRanked, elem)->m_value, std::max(zoom, 1));
else
setMinZoom(elem->m_value, std::max(zoom, 1));
}
quadBeg = quadEnd;
quadIndex += quadIncrement;
}
ASSERT(quadBeg == treeEnd, ());
return topRanked;
};
// Root level is not encoded, so start depth from 1
if (auto const elem = traverse(traverse, m_quadtree.begin(), m_quadtree.end(), 1 /* depth */))
setMinZoom(elem->m_value, 1 /* zoom */); // at least one element is visible from lowest zoom level
else
CHECK(false, (m_quadtree.size()));
}
private:
struct Element
{
template <typename V>
Element(m2::PointD const & point, V && value) : m_point{point}
, m_value{std::forward<V>(value)}
{}
bool operator<(Element const & rhs) const { return m_zCurveCoord < rhs.m_zCurveCoord; }
m2::PointD m_point;
Value m_value;
uint64_t m_zCurveCoord = 0; // z-order curve coordiante
};
Less m_less;
std::vector<Element> m_quadtree;
};
} // namespace kml

View file

@ -0,0 +1,36 @@
project(pykmlib)
set(
SRC
bindings.cpp
)
include_directories(${CMAKE_BINARY_DIR})
omim_add_library(${PROJECT_NAME} MODULE ${SRC})
omim_link_libraries(
${PROJECT_NAME}
${Boost_LIBRARIES}
kml
indexer
editor
platform
coding
geometry
base
ICU::i18n
cppjansson
protobuf
pugixml
expat::expat
succinct
)
link_qt5_core(${PROJECT_NAME})
if (PLATFORM_MAC)
omim_link_libraries(${PROJECT_NAME} "-Wl,-undefined,dynamic_lookup")
endif()
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")

View file

@ -0,0 +1,938 @@
#include "kml/serdes.hpp"
#include "kml/serdes_binary.hpp"
#include "indexer/classificator.hpp"
#include "indexer/classificator_loader.hpp"
#include "coding/string_utf8_multilang.hpp"
#include "geometry/latlon.hpp"
#include "geometry/mercator.hpp"
#include "geometry/point_with_altitude.hpp"
#include "base/assert.hpp"
// This header should be included due to a python compilation error.
// pyport.h overwrites defined macros and replaces it with its own.
// However, in the OS X c++ libraries, these are not macros but functions,
// hence the error. See https://bugs.python.org/issue10910
#include <exception>
#include <locale>
#include <sstream>
#include <string>
#include <type_traits>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreorder"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-local-typedef"
#endif
#include "pyhelpers/module_version.hpp"
#include "pyhelpers/vector_list_conversion.hpp"
#include "pyhelpers/vector_uint8.hpp"
#include <boost/python.hpp>
#include <boost/python/args.hpp>
#include <boost/python/dict.hpp>
#include <boost/python/enum.hpp>
#include <boost/python/exception_translator.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#pragma GCC diagnostic pop
using namespace kml;
using namespace boost::python;
namespace
{
struct LocalizableStringAdapter
{
static std::string const & Get(LocalizableString const & str, std::string const & lang)
{
auto const langIndex = StringUtf8Multilang::GetLangIndex(lang);
auto const it = str.find(langIndex);
if (it != str.end())
return it->second;
throw std::runtime_error("Language not found. lang: " + lang);
}
static void Set(LocalizableString & str, std::string const & lang, std::string const & val)
{
auto const langIndex = StringUtf8Multilang::GetLangIndex(lang);
if (langIndex == StringUtf8Multilang::kUnsupportedLanguageCode)
throw std::runtime_error("Unsupported language. lang: " + lang);
str[langIndex] = val;
}
static void Delete(LocalizableString & str, std::string const & lang)
{
auto const langIndex = StringUtf8Multilang::GetLangIndex(lang);
auto const it = str.find(langIndex);
if (it != str.end())
str.erase(it);
else
throw std::runtime_error("Language not found. lang: " + lang);
}
static boost::python::dict GetDict(LocalizableString const & str)
{
boost::python::dict d;
for (auto const & s : str)
{
std::string lang = StringUtf8Multilang::GetLangByCode(s.first);
if (lang.empty())
throw std::runtime_error("Language not found");
d[lang] = s.second;
}
return d;
}
static void SetDict(LocalizableString & str, boost::python::dict & dict)
{
str.clear();
if (dict.is_none())
return;
auto const langs = pyhelpers::PythonListToStdVector<std::string>(dict.keys());
for (auto const & lang : langs)
{
auto const langIndex = StringUtf8Multilang::GetLangIndex(lang);
if (langIndex == StringUtf8Multilang::kUnsupportedLanguageCode)
throw std::runtime_error("Unsupported language. lang: " + lang);
str[langIndex] = extract<std::string>(dict[lang]);
}
}
static std::string ToString(LocalizableString const & str)
{
std::ostringstream out;
out << "[";
for (auto it = str.begin(); it != str.end(); ++it)
{
out << "'" << StringUtf8Multilang::GetLangByCode(it->first) << "':'" << it->second << "'";
auto it2 = it;
it2++;
if (it2 != str.end())
out << ", ";
}
out << "]";
return out.str();
}
};
std::string LatLonToString(ms::LatLon const & latLon);
struct PointWithAltitudeAdapter
{
static m2::PointD const & GetPoint(geometry::PointWithAltitude const & ptWithAlt) { return ptWithAlt.GetPoint(); }
static geometry::Altitude GetAltitude(geometry::PointWithAltitude const & ptWithAlt)
{
return ptWithAlt.GetAltitude();
}
static void SetPoint(geometry::PointWithAltitude & ptWithAlt, m2::PointD const & pt) { ptWithAlt.SetPoint(pt); }
static void SetAltitude(geometry::PointWithAltitude & ptWithAlt, geometry::Altitude altitude)
{
ptWithAlt.SetAltitude(altitude);
}
static std::string ToString(geometry::PointWithAltitude const & ptWithAlt)
{
auto const latLon = mercator::ToLatLon(ptWithAlt.GetPoint());
std::ostringstream out;
out << "["
<< "point:" << LatLonToString(latLon) << ", "
<< "altitude:" << ptWithAlt.GetAltitude() << "]";
return out.str();
}
};
struct PropertiesAdapter
{
static std::string const & Get(Properties const & props, std::string const & key)
{
auto const it = props.find(key);
if (it != props.end())
return it->second;
throw std::runtime_error("Property not found. key: " + key);
}
static void Set(Properties & props, std::string const & key, std::string const & val) { props[key] = val; }
static void Delete(Properties & props, std::string const & key)
{
auto const it = props.find(key);
if (it != props.end())
props.erase(it);
else
throw std::runtime_error("Property not found. key: " + key);
}
static boost::python::dict GetDict(Properties const & props)
{
boost::python::dict d;
for (auto const & p : props)
d[p.first] = p.second;
return d;
}
static void SetDict(Properties & props, boost::python::dict & dict)
{
props.clear();
if (dict.is_none())
return;
auto const keys = pyhelpers::PythonListToStdVector<std::string>(dict.keys());
for (auto const & k : keys)
props[k] = extract<std::string>(dict[k]);
}
static std::string ToString(Properties const & props)
{
std::ostringstream out;
out << "[";
for (auto it = props.begin(); it != props.end(); ++it)
{
out << "'" << it->first << "':'" << it->second << "'";
auto it2 = it;
it2++;
if (it2 != props.end())
out << ", ";
}
out << "]";
return out.str();
}
};
template <typename T>
struct VectorAdapter
{
static boost::python::list Get(std::vector<T> const & v) { return pyhelpers::StdVectorToPythonList(v); }
static void Set(std::vector<T> & v, boost::python::object const & iterable)
{
if (iterable.is_none())
{
v.clear();
return;
}
v = pyhelpers::PythonListToStdVector<T>(iterable);
}
static void PrintType(std::ostringstream & out, T const & t) { out << t; }
static std::string ToString(std::vector<T> const & v)
{
std::ostringstream out;
out << "[";
for (size_t i = 0; i < v.size(); ++i)
{
PrintType(out, v[i]);
if (i + 1 != v.size())
out << ", ";
}
out << "]";
return out.str();
}
};
template <>
void VectorAdapter<uint8_t>::PrintType(std::ostringstream & out, uint8_t const & t)
{
out << static_cast<uint32_t>(t);
}
template <>
void VectorAdapter<std::string>::PrintType(std::ostringstream & out, std::string const & s)
{
out << "'" << s << "'";
}
std::string TrackLayerToString(TrackLayer const & trackLayer);
template <>
void VectorAdapter<TrackLayer>::PrintType(std::ostringstream & out, TrackLayer const & t)
{
out << TrackLayerToString(t);
}
template <>
void VectorAdapter<geometry::PointWithAltitude>::PrintType(std::ostringstream & out,
geometry::PointWithAltitude const & pt)
{
out << PointWithAltitudeAdapter::ToString(pt);
}
std::string BookmarkDataToString(BookmarkData const & bm);
template <>
void VectorAdapter<BookmarkData>::PrintType(std::ostringstream & out, BookmarkData const & bm)
{
out << BookmarkDataToString(bm);
}
std::string TrackDataToString(TrackData const & t);
template <>
void VectorAdapter<TrackData>::PrintType(std::ostringstream & out, TrackData const & t)
{
out << TrackDataToString(t);
}
std::string CategoryDataToString(CategoryData const & c);
template <>
void VectorAdapter<CategoryData>::PrintType(std::ostringstream & out, CategoryData const & c)
{
out << CategoryDataToString(c);
}
std::string PredefinedColorToString(PredefinedColor c)
{
switch (c)
{
case PredefinedColor::None: return "NONE";
case PredefinedColor::Red: return "RED";
case PredefinedColor::Blue: return "BLUE";
case PredefinedColor::Purple: return "PURPLE";
case PredefinedColor::Yellow: return "YELLOW";
case PredefinedColor::Pink: return "PINK";
case PredefinedColor::Brown: return "BROWN";
case PredefinedColor::Green: return "GREEN";
case PredefinedColor::Orange: return "ORANGE";
case PredefinedColor::DeepPurple: return "DEEPPURPLE";
case PredefinedColor::LightBlue: return "LIGHTBLUE";
case PredefinedColor::Cyan: return "CYAN";
case PredefinedColor::Teal: return "TEAL";
case PredefinedColor::Lime: return "LIME";
case PredefinedColor::DeepOrange: return "DEEPORANGE";
case PredefinedColor::Gray: return "GRAY";
case PredefinedColor::BlueGray: return "BLUEGRAY";
case PredefinedColor::Count: CHECK(false, ("Unknown predefined color")); return {};
}
}
std::string AccessRulesToString(AccessRules accessRules)
{
switch (accessRules)
{
case AccessRules::Local: return "LOCAL";
case AccessRules::DirectLink: return "DIRECT_LINK";
case AccessRules::P2P: return "P2P";
case AccessRules::Paid: return "PAID";
case AccessRules::Public: return "PUBLIC";
case AccessRules::AuthorOnly: return "AUTHOR_ONLY";
case AccessRules::Count: CHECK(false, ("Unknown access rules")); return {};
}
}
std::string BookmarkIconToString(BookmarkIcon icon)
{
switch (icon)
{
case BookmarkIcon::None: return "NONE";
case BookmarkIcon::Hotel: return "HOTEL";
case BookmarkIcon::Animals: return "ANIMALS";
case BookmarkIcon::Buddhism: return "BUDDHISM";
case BookmarkIcon::Building: return "BUILDING";
case BookmarkIcon::Christianity: return "CHRISTIANITY";
case BookmarkIcon::Entertainment: return "ENTERTAINMENT";
case BookmarkIcon::Exchange: return "EXCHANGE";
case BookmarkIcon::Food: return "FOOD";
case BookmarkIcon::Gas: return "GAS";
case BookmarkIcon::Judaism: return "JUDAISM";
case BookmarkIcon::Medicine: return "MEDICINE";
case BookmarkIcon::Mountain: return "MOUNTAIN";
case BookmarkIcon::Museum: return "MUSEUM";
case BookmarkIcon::Islam: return "ISLAM";
case BookmarkIcon::Park: return "PARK";
case BookmarkIcon::Parking: return "PARKING";
case BookmarkIcon::Shop: return "SHOP";
case BookmarkIcon::Sights: return "SIGHTS";
case BookmarkIcon::Swim: return "SWIM";
case BookmarkIcon::Water: return "WATER";
case BookmarkIcon::Bar: return "BAR";
case BookmarkIcon::Transport: return "TRANSPORT";
case BookmarkIcon::Viewpoint: return "VIEWPOINT";
case BookmarkIcon::Sport: return "SPORT";
case BookmarkIcon::Start: return "START";
case BookmarkIcon::Finish: return "FINISH";
case BookmarkIcon::Count: CHECK(false, ("Unknown bookmark icon")); return {};
}
}
std::string ColorDataToString(ColorData const & c)
{
std::ostringstream out;
out << "["
<< "predefined_color:" << PredefinedColorToString(c.m_predefinedColor) << ", "
<< "rgba:" << c.m_rgba << "]";
return out.str();
}
std::string LatLonToString(ms::LatLon const & latLon)
{
std::ostringstream out;
out << "["
<< "lat:" << latLon.m_lat << ", "
<< "lon:" << latLon.m_lon << "]";
return out.str();
}
std::string CompilationTypeToString(CompilationType compilationType)
{
switch (compilationType)
{
case CompilationType::Category: return "Category";
case CompilationType::Collection: return "Collection";
case CompilationType::Day: return "Day";
case CompilationType::Count: CHECK(false, ("Unknown access rules")); return {};
}
}
std::string BookmarkDataToString(BookmarkData const & bm)
{
std::ostringstream out;
ms::LatLon const latLon(mercator::YToLat(bm.m_point.y), mercator::XToLon(bm.m_point.x));
out << "["
<< "name:" << LocalizableStringAdapter::ToString(bm.m_name) << ", "
<< "description:" << LocalizableStringAdapter::ToString(bm.m_description) << ", "
<< "feature_types:" << VectorAdapter<uint32_t>::ToString(bm.m_featureTypes) << ", "
<< "custom_name:" << LocalizableStringAdapter::ToString(bm.m_customName) << ", "
<< "color:" << ColorDataToString(bm.m_color) << ", "
<< "icon:" << BookmarkIconToString(bm.m_icon) << ", "
<< "viewport_scale:" << static_cast<uint32_t>(bm.m_viewportScale) << ", "
<< "timestamp:" << DebugPrint(bm.m_timestamp) << ", "
<< "point:" << LatLonToString(latLon) << ", "
<< "bound_tracks:" << VectorAdapter<uint8_t>::ToString(bm.m_boundTracks) << ", "
<< "visible:" << (bm.m_visible ? "True" : "False") << ", "
<< "nearest_toponym:'" << bm.m_nearestToponym << "', "
<< "compilations:" << VectorAdapter<uint64_t>::ToString(bm.m_compilations) << ", "
<< "properties:" << PropertiesAdapter::ToString(bm.m_properties) << "]";
return out.str();
}
std::string TrackLayerToString(TrackLayer const & trackLayer)
{
std::ostringstream out;
out << "["
<< "line_width:" << trackLayer.m_lineWidth << ", "
<< "color:" << ColorDataToString(trackLayer.m_color) << "]";
return out.str();
}
std::string TrackDataToString(TrackData const & t)
{
std::ostringstream out;
out << "["
<< "local_id:" << static_cast<uint32_t>(t.m_localId) << ", "
<< "name:" << LocalizableStringAdapter::ToString(t.m_name) << ", "
<< "description:" << LocalizableStringAdapter::ToString(t.m_description) << ", "
<< "timestamp:" << DebugPrint(t.m_timestamp) << ", "
<< "layers:" << VectorAdapter<TrackLayer>::ToString(t.m_layers) << ", "
<< "points_with_altitudes:" << VectorAdapter<geometry::PointWithAltitude>::ToString(t.m_pointsWithAltitudes)
<< ", "
<< "visible:" << (t.m_visible ? "True" : "False") << ", "
<< "nearest_toponyms:" << VectorAdapter<std::string>::ToString(t.m_nearestToponyms) << ", "
<< "properties:" << PropertiesAdapter::ToString(t.m_properties) << "]";
return out.str();
}
std::string LanguagesListToString(std::vector<int8_t> const & langs)
{
std::ostringstream out;
out << "[";
for (size_t i = 0; i < langs.size(); ++i)
{
out << "'" << StringUtf8Multilang::GetLangByCode(langs[i]) << "'";
if (i + 1 != langs.size())
out << ", ";
}
out << "]";
return out.str();
}
std::string CategoryDataToString(CategoryData const & c)
{
std::ostringstream out;
out << "["
<< "type:" << CompilationTypeToString(c.m_type) << ", "
<< "compilation_id:" << c.m_compilationId << ", "
<< "name:" << LocalizableStringAdapter::ToString(c.m_name) << ", "
<< "annotation:" << LocalizableStringAdapter::ToString(c.m_annotation) << ", "
<< "description:" << LocalizableStringAdapter::ToString(c.m_description) << ", "
<< "image_url:'" << c.m_imageUrl << "', "
<< "visible:" << (c.m_visible ? "True" : "False") << ", "
<< "author_name:'" << c.m_authorName << "', "
<< "author_id:'" << c.m_authorId << "', "
<< "last_modified:" << DebugPrint(c.m_lastModified) << ", "
<< "rating:" << c.m_rating << ", "
<< "reviews_number:" << c.m_reviewsNumber << ", "
<< "access_rules:" << AccessRulesToString(c.m_accessRules) << ", "
<< "tags:" << VectorAdapter<std::string>::ToString(c.m_tags) << ", "
<< "toponyms:" << VectorAdapter<std::string>::ToString(c.m_toponyms) << ", "
<< "languages:" << LanguagesListToString(c.m_languageCodes) << ", "
<< "properties:" << PropertiesAdapter::ToString(c.m_properties) << "]";
return out.str();
}
std::string FileDataToString(FileData const & fd)
{
std::ostringstream out;
out << "["
<< "server_id:" << fd.m_serverId << ", "
<< "category:" << CategoryDataToString(fd.m_categoryData) << ", "
<< "bookmarks:" << VectorAdapter<BookmarkData>::ToString(fd.m_bookmarksData) << ", "
<< "tracks:" << VectorAdapter<TrackData>::ToString(fd.m_tracksData) << ", "
<< "compilations:" << VectorAdapter<CategoryData>::ToString(fd.m_compilationsData) << "]";
return out.str();
}
struct TimestampConverter
{
TimestampConverter() { converter::registry::push_back(&convertible, &construct, type_id<Timestamp>()); }
static void * convertible(PyObject * objPtr)
{
extract<uint64_t> checker(objPtr);
if (!checker.check())
return nullptr;
return objPtr;
}
static void construct(PyObject * objPtr, converter::rvalue_from_python_stage1_data * data)
{
auto const ts = FromSecondsSinceEpoch(extract<uint64_t>(objPtr));
void * storage = reinterpret_cast<converter::rvalue_from_python_storage<Timestamp> *>(data)->storage.bytes;
new (storage) Timestamp(ts);
data->convertible = storage;
}
};
struct LatLonConverter
{
LatLonConverter() { converter::registry::push_back(&convertible, &construct, type_id<m2::PointD>()); }
static void * convertible(PyObject * objPtr)
{
extract<ms::LatLon> checker(objPtr);
if (!checker.check())
return nullptr;
return objPtr;
}
static void construct(PyObject * objPtr, converter::rvalue_from_python_stage1_data * data)
{
ms::LatLon latLon = extract<ms::LatLon>(objPtr);
m2::PointD pt(mercator::LonToX(latLon.m_lon), mercator::LatToY(latLon.m_lat));
void * storage = reinterpret_cast<converter::rvalue_from_python_storage<m2::PointD> *>(data)->storage.bytes;
new (storage) m2::PointD(pt);
data->convertible = storage;
}
};
void TranslateRuntimeError(std::runtime_error const & e)
{
PyErr_SetString(PyExc_RuntimeError, e.what());
}
boost::python::list GetLanguages(std::vector<int8_t> const & langs)
{
std::vector<std::string> result;
result.reserve(langs.size());
for (auto const & langCode : langs)
{
std::string lang = StringUtf8Multilang::GetLangByCode(langCode);
if (lang.empty())
throw std::runtime_error("Language not found. langCode: " + std::to_string(langCode));
result.emplace_back(std::move(lang));
}
return pyhelpers::StdVectorToPythonList(result);
}
void SetLanguages(std::vector<int8_t> & langs, boost::python::object const & iterable)
{
langs.clear();
if (iterable.is_none())
return;
auto const langStr = pyhelpers::PythonListToStdVector<std::string>(iterable);
langs.reserve(langStr.size());
for (auto const & lang : langStr)
{
auto const langIndex = StringUtf8Multilang::GetLangIndex(lang);
if (langIndex == StringUtf8Multilang::kUnsupportedLanguageCode)
throw std::runtime_error("Unsupported language. lang: " + lang);
langs.emplace_back(langIndex);
}
}
boost::python::list GetSupportedLanguages()
{
auto const & supportedLangs = StringUtf8Multilang::GetSupportedLanguages();
std::vector<std::string> langs;
langs.reserve(supportedLangs.size());
for (auto const & lang : supportedLangs)
langs.emplace_back(lang.m_code);
return pyhelpers::StdVectorToPythonList(langs);
}
boost::python::object GetLanguageIndex(std::string const & lang)
{
auto const langIndex = StringUtf8Multilang::GetLangIndex(lang);
if (langIndex == StringUtf8Multilang::kUnsupportedLanguageCode)
throw std::runtime_error("Unsupported language. lang: " + lang);
return boost::python::object(langIndex);
}
std::string ExportKml(FileData const & fileData)
{
std::string resultBuffer;
try
{
MemWriter<decltype(resultBuffer)> sink(resultBuffer);
kml::SerializerKml ser(fileData);
ser.Serialize(sink);
}
catch (kml::SerializerKml::SerializeException const & exc)
{
throw std::runtime_error(std::string("Export error: ") + exc.what());
}
return resultBuffer;
}
FileData ImportKml(std::string const & str)
{
kml::FileData data;
try
{
kml::DeserializerKml des(data);
MemReader reader(str.data(), str.size());
des.Deserialize(reader);
}
catch (kml::DeserializerKml::DeserializeException const & exc)
{
throw std::runtime_error(std::string("Import error: ") + exc.what());
}
return data;
}
void LoadClassificatorTypes(std::string const & classificatorFileStr, std::string const & typesFileStr)
{
classificator::LoadTypes(classificatorFileStr, typesFileStr);
}
uint32_t ClassificatorTypeToIndex(std::string const & typeStr)
{
if (typeStr.empty())
throw std::runtime_error("Empty type is not allowed.");
auto const & c = classif();
if (!c.HasTypesMapping())
throw std::runtime_error("Types mapping is not loaded. typeStr: " + typeStr);
auto const type = c.GetTypeByReadableObjectName(typeStr);
if (!c.IsTypeValid(type))
throw std::runtime_error("Type is not valid. typeStr: " + typeStr);
return c.GetIndexForType(type);
}
std::string IndexToClassificatorType(uint32_t index)
{
auto const & c = classif();
if (!c.HasTypesMapping())
throw std::runtime_error("Types mapping is not loaded.");
uint32_t t;
try
{
t = c.GetTypeForIndex(index);
}
catch (std::out_of_range const & exc)
{
throw std::runtime_error("Type is not found. index: " + std::to_string(index));
}
if (!c.IsTypeValid(t))
throw std::runtime_error("Type is not valid. type: " + std::to_string(t));
return c.GetReadableObjectName(t);
}
} // namespace
BOOST_PYTHON_MODULE(pykmlib)
{
scope().attr("__version__") = PYBINDINGS_VERSION;
scope().attr("invalid_altitude") = geometry::kInvalidAltitude;
register_exception_translator<std::runtime_error>(&TranslateRuntimeError);
TimestampConverter();
LatLonConverter();
enum_<PredefinedColor>("PredefinedColor")
.value(PredefinedColorToString(PredefinedColor::None).c_str(), PredefinedColor::None)
.value(PredefinedColorToString(PredefinedColor::Red).c_str(), PredefinedColor::Red)
.value(PredefinedColorToString(PredefinedColor::Blue).c_str(), PredefinedColor::Blue)
.value(PredefinedColorToString(PredefinedColor::Purple).c_str(), PredefinedColor::Purple)
.value(PredefinedColorToString(PredefinedColor::Yellow).c_str(), PredefinedColor::Yellow)
.value(PredefinedColorToString(PredefinedColor::Pink).c_str(), PredefinedColor::Pink)
.value(PredefinedColorToString(PredefinedColor::Brown).c_str(), PredefinedColor::Brown)
.value(PredefinedColorToString(PredefinedColor::Green).c_str(), PredefinedColor::Green)
.value(PredefinedColorToString(PredefinedColor::Orange).c_str(), PredefinedColor::Orange)
.value(PredefinedColorToString(PredefinedColor::DeepPurple).c_str(), PredefinedColor::DeepPurple)
.value(PredefinedColorToString(PredefinedColor::LightBlue).c_str(), PredefinedColor::LightBlue)
.value(PredefinedColorToString(PredefinedColor::Cyan).c_str(), PredefinedColor::Cyan)
.value(PredefinedColorToString(PredefinedColor::Teal).c_str(), PredefinedColor::Teal)
.value(PredefinedColorToString(PredefinedColor::Lime).c_str(), PredefinedColor::Lime)
.value(PredefinedColorToString(PredefinedColor::DeepOrange).c_str(), PredefinedColor::DeepOrange)
.value(PredefinedColorToString(PredefinedColor::Gray).c_str(), PredefinedColor::Gray)
.value(PredefinedColorToString(PredefinedColor::BlueGray).c_str(), PredefinedColor::BlueGray)
.export_values();
enum_<AccessRules>("AccessRules")
.value(AccessRulesToString(AccessRules::Local).c_str(), AccessRules::Local)
.value(AccessRulesToString(AccessRules::DirectLink).c_str(), AccessRules::DirectLink)
.value(AccessRulesToString(AccessRules::P2P).c_str(), AccessRules::P2P)
.value(AccessRulesToString(AccessRules::Paid).c_str(), AccessRules::Paid)
.value(AccessRulesToString(AccessRules::Public).c_str(), AccessRules::Public)
.value(AccessRulesToString(AccessRules::AuthorOnly).c_str(), AccessRules::AuthorOnly)
.export_values();
enum_<BookmarkIcon>("BookmarkIcon")
.value(BookmarkIconToString(BookmarkIcon::None).c_str(), BookmarkIcon::None)
.value(BookmarkIconToString(BookmarkIcon::Hotel).c_str(), BookmarkIcon::Hotel)
.value(BookmarkIconToString(BookmarkIcon::Animals).c_str(), BookmarkIcon::Animals)
.value(BookmarkIconToString(BookmarkIcon::Buddhism).c_str(), BookmarkIcon::Buddhism)
.value(BookmarkIconToString(BookmarkIcon::Building).c_str(), BookmarkIcon::Building)
.value(BookmarkIconToString(BookmarkIcon::Christianity).c_str(), BookmarkIcon::Christianity)
.value(BookmarkIconToString(BookmarkIcon::Entertainment).c_str(), BookmarkIcon::Entertainment)
.value(BookmarkIconToString(BookmarkIcon::Exchange).c_str(), BookmarkIcon::Exchange)
.value(BookmarkIconToString(BookmarkIcon::Food).c_str(), BookmarkIcon::Food)
.value(BookmarkIconToString(BookmarkIcon::Gas).c_str(), BookmarkIcon::Gas)
.value(BookmarkIconToString(BookmarkIcon::Judaism).c_str(), BookmarkIcon::Judaism)
.value(BookmarkIconToString(BookmarkIcon::Medicine).c_str(), BookmarkIcon::Medicine)
.value(BookmarkIconToString(BookmarkIcon::Mountain).c_str(), BookmarkIcon::Mountain)
.value(BookmarkIconToString(BookmarkIcon::Museum).c_str(), BookmarkIcon::Museum)
.value(BookmarkIconToString(BookmarkIcon::Islam).c_str(), BookmarkIcon::Islam)
.value(BookmarkIconToString(BookmarkIcon::Park).c_str(), BookmarkIcon::Park)
.value(BookmarkIconToString(BookmarkIcon::Parking).c_str(), BookmarkIcon::Parking)
.value(BookmarkIconToString(BookmarkIcon::Shop).c_str(), BookmarkIcon::Shop)
.value(BookmarkIconToString(BookmarkIcon::Sights).c_str(), BookmarkIcon::Sights)
.value(BookmarkIconToString(BookmarkIcon::Swim).c_str(), BookmarkIcon::Swim)
.value(BookmarkIconToString(BookmarkIcon::Water).c_str(), BookmarkIcon::Water)
.value(BookmarkIconToString(BookmarkIcon::Bar).c_str(), BookmarkIcon::Bar)
.value(BookmarkIconToString(BookmarkIcon::Transport).c_str(), BookmarkIcon::Transport)
.value(BookmarkIconToString(BookmarkIcon::Viewpoint).c_str(), BookmarkIcon::Viewpoint)
.value(BookmarkIconToString(BookmarkIcon::Sport).c_str(), BookmarkIcon::Sport)
.value(BookmarkIconToString(BookmarkIcon::Start).c_str(), BookmarkIcon::Start)
.value(BookmarkIconToString(BookmarkIcon::Finish).c_str(), BookmarkIcon::Finish)
.export_values();
enum_<CompilationType>("CompilationType")
.value(CompilationTypeToString(CompilationType::Category).c_str(), CompilationType::Category)
.value(CompilationTypeToString(CompilationType::Collection).c_str(), CompilationType::Collection)
.value(CompilationTypeToString(CompilationType::Day).c_str(), CompilationType::Day)
.export_values();
class_<ColorData>("ColorData")
.def_readwrite("predefined_color", &ColorData::m_predefinedColor)
.def_readwrite("rgba", &ColorData::m_rgba)
.def("__eq__", &ColorData::operator==)
.def("__ne__", &ColorData::operator!=)
.def("__str__", &ColorDataToString);
class_<LocalizableString>("LocalizableString")
.def("__len__", &LocalizableString::size)
.def("clear", &LocalizableString::clear)
.def("__getitem__", &LocalizableStringAdapter::Get, return_value_policy<copy_const_reference>())
.def("__setitem__", &LocalizableStringAdapter::Set, with_custodian_and_ward<1, 2>())
.def("__delitem__", &LocalizableStringAdapter::Delete)
.def("get_dict", &LocalizableStringAdapter::GetDict)
.def("set_dict", &LocalizableStringAdapter::SetDict)
.def("__str__", &LocalizableStringAdapter::ToString);
class_<std::vector<std::string>>("StringList")
.def(vector_indexing_suite<std::vector<std::string>>())
.def("get_list", &VectorAdapter<std::string>::Get)
.def("set_list", &VectorAdapter<std::string>::Set)
.def("__str__", &VectorAdapter<std::string>::ToString);
class_<std::vector<uint64_t>>("Uint64List")
.def(vector_indexing_suite<std::vector<uint64_t>>())
.def("get_list", &VectorAdapter<uint64_t>::Get)
.def("set_list", &VectorAdapter<uint64_t>::Set)
.def("__str__", &VectorAdapter<uint64_t>::ToString);
class_<std::vector<uint32_t>>("Uint32List")
.def(vector_indexing_suite<std::vector<uint32_t>>())
.def("get_list", &VectorAdapter<uint32_t>::Get)
.def("set_list", &VectorAdapter<uint32_t>::Set)
.def("__str__", &VectorAdapter<uint32_t>::ToString);
class_<std::vector<uint8_t>>("Uint8List")
.def(vector_indexing_suite<std::vector<uint8_t>>())
.def("get_list", &VectorAdapter<uint8_t>::Get)
.def("set_list", &VectorAdapter<uint8_t>::Set)
.def("__str__", &VectorAdapter<uint8_t>::ToString);
class_<ms::LatLon>("LatLon", init<double, double>())
.def_readwrite("lat", &ms::LatLon::m_lat)
.def_readwrite("lon", &ms::LatLon::m_lon)
.def("__str__", &LatLonToString);
class_<geometry::PointWithAltitude>("PointWithAltitude")
.def("get_point", &PointWithAltitudeAdapter::GetPoint, return_value_policy<copy_const_reference>())
.def("set_point", &PointWithAltitudeAdapter::SetPoint)
.def("get_altitude", &PointWithAltitudeAdapter::GetAltitude)
.def("set_altitude", &PointWithAltitudeAdapter::SetAltitude)
.def("__str__", &PointWithAltitudeAdapter::ToString);
class_<m2::PointD>("PointD");
class_<Timestamp>("Timestamp");
class_<Properties>("Properties")
.def("__len__", &Properties::size)
.def("clear", &Properties::clear)
.def("__getitem__", &PropertiesAdapter::Get, return_value_policy<copy_const_reference>())
.def("__setitem__", &PropertiesAdapter::Set, with_custodian_and_ward<1, 2>())
.def("__delitem__", &PropertiesAdapter::Delete)
.def("get_dict", &PropertiesAdapter::GetDict)
.def("set_dict", &PropertiesAdapter::SetDict)
.def("__str__", &PropertiesAdapter::ToString);
class_<BookmarkData>("BookmarkData")
.def_readwrite("name", &BookmarkData::m_name)
.def_readwrite("description", &BookmarkData::m_description)
.def_readwrite("feature_types", &BookmarkData::m_featureTypes)
.def_readwrite("custom_name", &BookmarkData::m_customName)
.def_readwrite("color", &BookmarkData::m_color)
.def_readwrite("icon", &BookmarkData::m_icon)
.def_readwrite("viewport_scale", &BookmarkData::m_viewportScale)
.def_readwrite("timestamp", &BookmarkData::m_timestamp)
.def_readwrite("point", &BookmarkData::m_point)
.def_readwrite("bound_tracks", &BookmarkData::m_boundTracks)
.def_readwrite("visible", &BookmarkData::m_visible)
.def_readwrite("nearest_toponym", &BookmarkData::m_nearestToponym)
.def_readwrite("compilations", &BookmarkData::m_compilations)
.def_readwrite("properties", &BookmarkData::m_properties)
.def("__eq__", &BookmarkData::operator==)
.def("__ne__", &BookmarkData::operator!=)
.def("__str__", &BookmarkDataToString);
class_<TrackLayer>("TrackLayer")
.def_readwrite("line_width", &TrackLayer::m_lineWidth)
.def_readwrite("color", &TrackLayer::m_color)
.def("__eq__", &TrackLayer::operator==)
.def("__ne__", &TrackLayer::operator!=)
.def("__str__", &TrackLayerToString);
class_<std::vector<TrackLayer>>("TrackLayerList")
.def(vector_indexing_suite<std::vector<TrackLayer>>())
.def("get_list", &VectorAdapter<TrackLayer>::Get)
.def("set_list", &VectorAdapter<TrackLayer>::Set)
.def("__str__", &VectorAdapter<TrackLayer>::ToString);
class_<std::vector<m2::PointD>>("PointDList")
.def(vector_indexing_suite<std::vector<m2::PointD>>())
.def("get_list", &VectorAdapter<m2::PointD>::Get)
.def("set_list", &VectorAdapter<m2::PointD>::Set)
.def("__str__", &VectorAdapter<m2::PointD>::ToString);
class_<std::vector<geometry::PointWithAltitude>>("PointWithAltitudeList")
.def(vector_indexing_suite<std::vector<geometry::PointWithAltitude>>())
.def("get_list", &VectorAdapter<geometry::PointWithAltitude>::Get)
.def("set_list", &VectorAdapter<geometry::PointWithAltitude>::Set)
.def("__str__", &VectorAdapter<geometry::PointWithAltitude>::ToString);
class_<std::vector<ms::LatLon>>("LatLonList").def(vector_indexing_suite<std::vector<ms::LatLon>>());
class_<TrackData>("TrackData")
.def_readwrite("local_id", &TrackData::m_localId)
.def_readwrite("name", &TrackData::m_name)
.def_readwrite("description", &TrackData::m_description)
.def_readwrite("timestamp", &TrackData::m_timestamp)
.def_readwrite("layers", &TrackData::m_layers)
.def_readwrite("points_with_altitudes", &TrackData::m_pointsWithAltitudes)
.def_readwrite("visible", &TrackData::m_visible)
.def_readwrite("nearest_toponyms", &TrackData::m_nearestToponyms)
.def_readwrite("properties", &TrackData::m_properties)
.def("__eq__", &TrackData::operator==)
.def("__ne__", &TrackData::operator!=)
.def("__str__", &TrackDataToString);
class_<std::vector<int8_t>>("LanguageList")
.def(vector_indexing_suite<std::vector<int8_t>>())
.def("get_list", &GetLanguages)
.def("set_list", &SetLanguages)
.def("__str__", &LanguagesListToString);
class_<CategoryData>("CategoryData")
.def_readwrite("type", &CategoryData::m_type)
.def_readwrite("compilation_id", &CategoryData::m_compilationId)
.def_readwrite("name", &CategoryData::m_name)
.def_readwrite("annotation", &CategoryData::m_annotation)
.def_readwrite("description", &CategoryData::m_description)
.def_readwrite("image_url", &CategoryData::m_imageUrl)
.def_readwrite("visible", &CategoryData::m_visible)
.def_readwrite("author_name", &CategoryData::m_authorName)
.def_readwrite("author_id", &CategoryData::m_authorId)
.def_readwrite("last_modified", &CategoryData::m_lastModified)
.def_readwrite("rating", &CategoryData::m_rating)
.def_readwrite("reviews_number", &CategoryData::m_reviewsNumber)
.def_readwrite("access_rules", &CategoryData::m_accessRules)
.def_readwrite("tags", &CategoryData::m_tags)
.def_readwrite("toponyms", &CategoryData::m_toponyms)
.def_readwrite("languages", &CategoryData::m_languageCodes)
.def_readwrite("properties", &CategoryData::m_properties)
.def("__eq__", &CategoryData::operator==)
.def("__ne__", &CategoryData::operator!=)
.def("__str__", &CategoryDataToString);
class_<std::vector<BookmarkData>>("BookmarkList")
.def(vector_indexing_suite<std::vector<BookmarkData>>())
.def("get_list", &VectorAdapter<BookmarkData>::Get)
.def("set_list", &VectorAdapter<BookmarkData>::Set)
.def("__str__", &VectorAdapter<BookmarkData>::ToString);
class_<std::vector<TrackData>>("TrackList")
.def(vector_indexing_suite<std::vector<TrackData>>())
.def("get_list", &VectorAdapter<TrackData>::Get)
.def("set_list", &VectorAdapter<TrackData>::Set)
.def("__str__", &VectorAdapter<TrackData>::ToString);
class_<std::vector<CategoryData>>("CompilationList")
.def(vector_indexing_suite<std::vector<CategoryData>>())
.def("get_list", &VectorAdapter<CategoryData>::Get)
.def("set_list", &VectorAdapter<CategoryData>::Set)
.def("__str__", &VectorAdapter<CategoryData>::ToString);
class_<FileData>("FileData")
.def_readwrite("server_id", &FileData::m_serverId)
.def_readwrite("category", &FileData::m_categoryData)
.def_readwrite("bookmarks", &FileData::m_bookmarksData)
.def_readwrite("tracks", &FileData::m_tracksData)
.def_readwrite("compilations", &FileData::m_compilationsData)
.def("__eq__", &FileData::operator==)
.def("__ne__", &FileData::operator!=)
.def("__str__", &FileDataToString);
def("set_bookmarks_min_zoom", &SetBookmarksMinZoom);
def("get_supported_languages", GetSupportedLanguages);
def("get_language_index", GetLanguageIndex);
def("timestamp_to_int", &ToSecondsSinceEpoch);
def("point_to_latlon", &mercator::ToLatLon);
def("export_kml", ExportKml);
def("import_kml", ImportKml);
def("load_classificator_types", LoadClassificatorTypes);
def("classificator_type_to_index", ClassificatorTypeToIndex);
def("index_to_classificator_type", IndexToClassificatorType);
}

View file

@ -0,0 +1,122 @@
import unittest
import datetime
import pykmlib
class PyKmlibAdsTest(unittest.TestCase):
def test_smoke(self):
classificator_file_str = ''
with open('./data/classificator.txt', 'r') as classificator_file:
classificator_file_str = classificator_file.read()
types_file_str = ''
with open('./data/types.txt', 'r') as types_file:
types_file_str = types_file.read()
pykmlib.load_classificator_types(classificator_file_str, types_file_str)
def make_compilation():
c = pykmlib.CategoryData()
c.type = pykmlib.CompilationType.Category
c.name['default'] = 'Test category'
c.name['ru'] = 'Тестовая категория'
c.description['default'] = 'Test description'
c.description['ru'] = 'Тестовое описание'
c.annotation['default'] = 'Test annotation'
c.annotation['en'] = 'Test annotation'
c.image_url = 'https://localhost/123.png'
c.visible = True
c.author_name = 'Organic Maps'
c.author_id = '12345'
c.rating = 8.9
c.reviews_number = 567
c.last_modified = int(datetime.datetime.now().timestamp())
c.access_rules = pykmlib.AccessRules.PUBLIC
c.tags.set_list(['mountains', 'ski', 'snowboard'])
c.toponyms.set_list(['12345', '54321'])
c.languages.set_list(['en', 'ru', 'de'])
c.properties.set_dict({'property1':'value1', 'property2':'value2'})
return c
category = make_compilation()
bookmark = pykmlib.BookmarkData()
bookmark.name['default'] = 'Test bookmark'
bookmark.name['ru'] = 'Тестовая метка'
bookmark.description['default'] = 'Test bookmark description'
bookmark.description['ru'] = 'Тестовое описание метки'
bookmark.feature_types.set_list([
pykmlib.classificator_type_to_index('historic-castle'),
pykmlib.classificator_type_to_index('historic-memorial')])
bookmark.custom_name['default'] = 'Мое любимое место'
bookmark.custom_name['en'] = 'My favorite place'
bookmark.color.predefined_color = pykmlib.PredefinedColor.BLUE
bookmark.color.rgba = 0
bookmark.icon = pykmlib.BookmarkIcon.HOTEL
bookmark.viewport_scale = 15
bookmark.timestamp = int(datetime.datetime.now().timestamp())
bookmark.point = pykmlib.LatLon(45.9242, 56.8679)
bookmark.visible = True
bookmark.nearest_toponym = '12345'
bookmark.properties.set_dict({'bm_property1':'value1', 'bm_property2':'value2'})
bookmark.bound_tracks.set_list([0])
bookmark.compilations.set_list([1, 2, 3])
layer1 = pykmlib.TrackLayer()
layer1.line_width = 6.0
layer1.color.rgba = 0xff0000ff
layer2 = pykmlib.TrackLayer()
layer2.line_width = 7.0
layer2.color.rgba = 0x00ff00ff
track = pykmlib.TrackData()
track.local_id = 1
track.name['default'] = 'Test track'
track.name['ru'] = 'Тестовый трек'
track.description['default'] = 'Test track description'
track.description['ru'] = 'Тестовое описание трека'
track.timestamp = int(datetime.datetime.now().timestamp())
track.layers.set_list([layer1, layer2])
pt1 = pykmlib.PointWithAltitude()
pt1.set_point(pykmlib.LatLon(45.9242, 56.8679))
pt1.set_altitude(100)
pt2 = pykmlib.PointWithAltitude()
pt2.set_point(pykmlib.LatLon(45.2244, 56.2786))
pt2.set_altitude(110)
pt3 = pykmlib.PointWithAltitude()
pt3.set_point(pykmlib.LatLon(45.1964, 56.9832))
pt3.set_altitude(pykmlib.invalid_altitude)
track.points_with_altitudes.set_list([pt1, pt2, pt3])
track.visible = True
track.nearest_toponyms.set_list(['12345', '54321', '98765'])
track.properties.set_dict({'tr_property1':'value1', 'tr_property2':'value2'})
compilations = pykmlib.CompilationList()
compilation = make_compilation()
compilation.compilation_id = 1
compilations.append(compilation)
collection = make_compilation()
collection.compilation_id = 2
collection.type = pykmlib.CompilationType.Collection
compilations.append(collection)
day = make_compilation()
day.compilation_id = 3
day.type = pykmlib.CompilationType.Day
compilations.append(day)
file_data = pykmlib.FileData()
file_data.server_id = 'AAAA-BBBB-CCCC-DDDD'
file_data.category = category
file_data.bookmarks.append(bookmark)
file_data.tracks.append(track)
file_data.compilations = compilations
pykmlib.set_bookmarks_min_zoom(file_data, 1.0, 19)
s = pykmlib.export_kml(file_data)
imported_file_data = pykmlib.import_kml(s)
self.assertEqual(file_data, imported_file_data)
if __name__ == "__main__":
unittest.main()

13
libs/kml/pykmlib/setup.py Normal file
View file

@ -0,0 +1,13 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
module_dir = os.path.abspath(os.path.dirname(__file__))
sys.path.insert(0, os.path.join(module_dir, '..', '..'))
from pyhelpers.setup import setup_omim_pybinding
NAME = "pykmlib"
setup_omim_pybinding(name=NAME)

1416
libs/kml/serdes.cpp Normal file

File diff suppressed because it is too large Load diff

160
libs/kml/serdes.hpp Normal file
View file

@ -0,0 +1,160 @@
#pragma once
#include "kml/serdes_common.hpp"
#include "kml/types.hpp"
#include "coding/parse_xml.hpp"
#include "coding/reader.hpp"
#include "coding/writer.hpp"
#include "geometry/point2d.hpp"
#include "base/exception.hpp"
#include <string>
namespace kml
{
class KmlWriter
{
public:
DECLARE_EXCEPTION(WriteKmlException, RootException);
explicit KmlWriter(Writer & writer) : m_writer(writer) {}
void Write(FileData const & fileData);
private:
Writer & m_writer;
};
class SerializerKml
{
public:
DECLARE_EXCEPTION(SerializeException, RootException);
explicit SerializerKml(FileData const & fileData) : m_fileData(fileData) {}
template <typename Sink>
void Serialize(Sink & sink)
{
KmlWriter kmlWriter(sink);
kmlWriter.Write(m_fileData);
}
private:
FileData const & m_fileData;
};
class KmlParser
{
public:
explicit KmlParser(FileData & data);
/// @name Parser callback functions.
/// @{
bool Push(std::string name);
void AddAttr(std::string attr, std::string value);
void Pop(std::string_view tag);
void CharData(std::string & value);
/// @}
bool IsValidAttribute(std::string_view type, std::string const & value, std::string const & attrInLowerCase) const;
static kml::TrackLayer GetDefaultTrackLayer();
private:
std::string const & GetTagFromEnd(size_t n) const;
bool IsProcessTrackTag() const;
bool IsProcessTrackCoord() const;
enum GeometryType
{
GEOMETRY_TYPE_UNKNOWN,
GEOMETRY_TYPE_POINT,
GEOMETRY_TYPE_LINE
};
void ResetPoint();
void SetOrigin(std::string const & s);
static void ParseAndAddPoints(MultiGeometry::LineT & line, std::string_view s, char const * blockSeparator,
char const * coordSeparator);
void ParseLineString(std::string const & s);
bool MakeValid();
void ParseColor(std::string const & value);
bool GetColorForStyle(std::string const & styleUrl, uint32_t & color) const;
double GetTrackWidthForStyle(std::string const & styleUrl) const;
FileData & m_data;
CategoryData m_compilationData;
CategoryData * m_categoryData; // never null
std::vector<std::string> m_tags;
GeometryType m_geometryType;
MultiGeometry m_geometry;
std::map<size_t, std::set<size_t>> m_skipTimes;
size_t m_lastTrackPointsCount;
uint32_t m_color;
std::string m_styleId;
std::string m_mapStyleId;
std::string m_styleUrlKey;
std::map<std::string, uint32_t> m_styleUrl2Color;
std::map<std::string, double> m_styleUrl2Width;
std::map<std::string, std::string> m_mapStyle2Style;
int8_t m_attrCode;
std::string m_attrId;
std::string m_attrKey;
LocalizableString m_name;
LocalizableString m_description;
PredefinedColor m_predefinedColor;
Timestamp m_timestamp;
m2::PointD m_org;
uint8_t m_viewportScale;
std::vector<uint32_t> m_featureTypes;
LocalizableString m_customName;
std::vector<LocalId> m_boundTracks;
LocalId m_localId;
BookmarkIcon m_icon;
std::vector<TrackLayer> m_trackLayers;
bool m_visible;
std::string m_nearestToponym;
std::vector<std::string> m_nearestToponyms;
int m_minZoom = 1;
kml::Properties m_properties;
std::vector<CompilationId> m_compilations;
double m_trackWidth;
};
class DeserializerKml
{
public:
DECLARE_EXCEPTION(DeserializeException, RootException);
explicit DeserializerKml(FileData & fileData);
template <typename ReaderType>
void Deserialize(ReaderType const & reader)
{
NonOwningReaderSource src(reader);
KmlParser parser(m_fileData);
if (!ParseXML(src, parser, true))
{
// Print corrupted KML file for debug and restore purposes.
std::string kmlText;
reader.ReadAsString(kmlText);
if (!kmlText.empty() && kmlText[0] == '<')
LOG(LWARNING, (kmlText));
MYTHROW(DeserializeException, ("Could not parse KML."));
}
}
private:
FileData & m_fileData;
};
} // namespace kml

View file

@ -0,0 +1,36 @@
#include "kml/serdes_binary.hpp"
namespace kml
{
namespace binary
{
SerializerKml::SerializerKml(FileData & data) : m_data(data)
{
ClearCollectionIndex();
// Collect all strings and substitute each for index.
auto const avgSz = data.m_bookmarksData.size() * 2 + data.m_tracksData.size() * 2 + 10;
LocalizableStringCollector collector(avgSz);
CollectorVisitor<decltype(collector)> visitor(collector);
m_data.Visit(visitor);
m_strings = collector.StealCollection();
}
SerializerKml::~SerializerKml()
{
ClearCollectionIndex();
}
void SerializerKml::ClearCollectionIndex()
{
LocalizableStringCollector collector(0);
CollectorVisitor<decltype(collector)> clearVisitor(collector, true /* clear index */);
m_data.Visit(clearVisitor);
}
DeserializerKml::DeserializerKml(FileData & data) : m_data(data)
{
m_data = {};
}
} // namespace binary
} // namespace kml

411
libs/kml/serdes_binary.hpp Normal file
View file

@ -0,0 +1,411 @@
#pragma once
#include "kml/header_binary.hpp"
#include "kml/types.hpp"
#include "kml/types_v3.hpp"
#include "kml/types_v6.hpp"
#include "kml/types_v7.hpp"
#include "kml/types_v8.hpp"
#include "kml/types_v8mm.hpp"
#include "kml/types_v9mm.hpp"
#include "kml/visitors.hpp"
#include "coding/text_storage.hpp"
#include <string>
#include <vector>
namespace kml
{
namespace binary
{
class SerializerKml
{
public:
explicit SerializerKml(FileData & data);
~SerializerKml();
void ClearCollectionIndex();
template <typename Sink>
void Serialize(Sink & sink)
{
// Write format version.
WriteToSink(sink, Version::Latest);
// Write device id.
{
auto const sz = static_cast<uint32_t>(m_data.m_deviceId.size());
WriteVarUint(sink, sz);
sink.Write(m_data.m_deviceId.data(), sz);
}
// Write server id.
{
auto const sz = static_cast<uint32_t>(m_data.m_serverId.size());
WriteVarUint(sink, sz);
sink.Write(m_data.m_serverId.data(), sz);
}
// Write bits count in double number.
WriteToSink(sink, kDoubleBits);
auto const startPos = sink.Pos();
// Reserve place for the header.
Header header;
WriteZeroesToSink(sink, header.Size());
// Serialize category.
header.m_categoryOffset = sink.Pos() - startPos;
SerializeCategory(sink);
// Serialize bookmarks.
header.m_bookmarksOffset = sink.Pos() - startPos;
SerializeBookmarks(sink);
// Serialize tracks.
header.m_tracksOffset = sink.Pos() - startPos;
SerializeTracks(sink);
// Serialize compilations.
header.m_compilationsOffset = sink.Pos() - startPos;
SerializeCompilations(sink);
// Serialize strings.
header.m_stringsOffset = sink.Pos() - startPos;
SerializeStrings(sink);
// Fill header.
header.m_eosOffset = sink.Pos() - startPos;
sink.Seek(startPos);
header.Serialize(sink);
sink.Seek(startPos + header.m_eosOffset);
}
template <typename Sink>
void SerializeCategory(Sink & sink)
{
CategorySerializerVisitor<Sink> visitor(sink, kDoubleBits);
visitor(m_data.m_categoryData);
}
template <typename Sink>
void SerializeBookmarks(Sink & sink)
{
BookmarkSerializerVisitor<Sink> visitor(sink, kDoubleBits);
visitor(m_data.m_bookmarksData);
}
template <typename Sink>
void SerializeTracks(Sink & sink)
{
BookmarkSerializerVisitor<Sink> visitor(sink, kDoubleBits);
visitor(m_data.m_tracksData);
}
template <typename Sink>
void SerializeCompilations(Sink & sink)
{
CategorySerializerVisitor<Sink> visitor(sink, kDoubleBits);
visitor(m_data.m_compilationsData);
}
// Serializes texts in a compressed storage with block access.
template <typename Sink>
void SerializeStrings(Sink & sink)
{
coding::BlockedTextStorageWriter<Sink> writer(sink, 200000 /* blockSize */);
for (auto const & str : m_strings)
writer.Append(str);
}
protected:
FileData & m_data;
std::vector<std::string> m_strings;
};
template <typename T, typename = void>
struct HasCompilationsData : std::false_type
{};
template <typename T>
struct HasCompilationsData<T, std::void_t<decltype(T::m_compilationsData)>>
: std::is_same<decltype(T::m_compilationsData), std::vector<CategoryData>>
{};
class DeserializerKml
{
public:
DECLARE_EXCEPTION(DeserializeException, RootException);
explicit DeserializerKml(FileData & data);
template <typename ReaderType>
void Deserialize(ReaderType const & reader)
{
// Check version.
NonOwningReaderSource source(reader);
m_header.m_version = ReadPrimitiveFromSource<Version>(source);
if (m_header.m_version != Version::V2 && m_header.m_version != Version::V3 && m_header.m_version != Version::V4 &&
m_header.m_version != Version::V5 && m_header.m_version != Version::V6 && m_header.m_version != Version::V7 &&
m_header.m_version != Version::V8 && m_header.m_version != Version::V9)
{
MYTHROW(DeserializeException, ("Incorrect file version."));
}
ReadDeviceId(source);
ReadServerId(source);
ReadBitsCountInDouble(source);
auto subReader = reader.CreateSubReader(source.Pos(), source.Size());
InitializeIfNeeded(*subReader);
switch (m_header.m_version)
{
case Version::Latest:
{
DeserializeFileData(subReader, m_data);
break;
}
case Version::V8:
{
FileDataV8 dataV8;
dataV8.m_deviceId = m_data.m_deviceId;
dataV8.m_serverId = m_data.m_serverId;
DeserializeFileData(subReader, dataV8);
m_data = dataV8.ConvertToLatestVersion();
break;
}
case Version::V8MM:
{
FileDataV8MM dataV8MM;
dataV8MM.m_deviceId = m_data.m_deviceId;
dataV8MM.m_serverId = m_data.m_serverId;
DeserializeFileData(subReader, dataV8MM);
m_data = dataV8MM.ConvertToLatestVersion();
break;
}
case Version::V9MM:
{
FileDataV9MM dataV9MM;
dataV9MM.m_deviceId = m_data.m_deviceId;
dataV9MM.m_serverId = m_data.m_serverId;
DeserializeFileData(subReader, dataV9MM);
m_data = dataV9MM.ConvertToLatestVersion();
break;
}
case Version::V7:
{
FileDataV7 dataV7;
dataV7.m_deviceId = m_data.m_deviceId;
dataV7.m_serverId = m_data.m_serverId;
DeserializeFileData(subReader, dataV7);
m_data = dataV7.ConvertToLatestVersion();
break;
}
case Version::V6:
case Version::V5:
case Version::V4:
{
// NOTE: v.4, v.5 and v.6 are binary compatible.
FileDataV6 dataV6;
dataV6.m_deviceId = m_data.m_deviceId;
dataV6.m_serverId = m_data.m_serverId;
DeserializeFileData(subReader, dataV6);
m_data = dataV6.ConvertToLatestVersion();
break;
}
case Version::V3:
case Version::V2:
{
// NOTE: v.2 and v.3 are binary compatible.
FileDataV3 dataV3;
dataV3.m_deviceId = m_data.m_deviceId;
dataV3.m_serverId = m_data.m_serverId;
DeserializeFileData(subReader, dataV3);
// Migrate bookmarks (it's necessary ony for v.2).
if (m_header.m_version == Version::V2)
MigrateBookmarksV2(dataV3);
m_data = dataV3.ConvertToLatestVersion();
break;
}
default:
{
UNREACHABLE();
}
}
}
private:
template <typename ReaderType>
void InitializeIfNeeded(ReaderType const & reader)
{
if (m_initialized)
return;
NonOwningReaderSource source(reader);
m_header.Deserialize(source);
if (m_header.m_version == Version::V8 || m_header.m_version == Version::V9)
{
// Check if file has Opensource V8/V9 or MapsMe V8/V9 format.
// Actual V8/V9 format has 6 offsets (uint64_t) in header. While V8MM/V9MM has 5 offsets.
// It means that first section (usually categories) has offset 0x28 = 40 = 5 * 8.
if (m_header.m_categoryOffset == 0x28 || m_header.m_bookmarksOffset == 0x28 || m_header.m_tracksOffset == 0x28 ||
m_header.m_stringsOffset == 0x28 || m_header.m_compilationsOffset == 0x28)
{
m_header.m_version = (m_header.m_version == Version::V8 ? Version::V8MM : Version::V9MM);
LOG(LINFO, ("KMB file has version", m_header.m_version));
m_header.m_eosOffset = m_header.m_stringsOffset;
m_header.m_stringsOffset = m_header.m_compilationsOffset;
}
}
m_initialized = true;
}
template <typename ReaderType>
std::unique_ptr<Reader> CreateSubReader(ReaderType const & reader, uint64_t startPos, uint64_t endPos)
{
ASSERT(m_initialized, ());
ASSERT_GREATER_OR_EQUAL(endPos, startPos, ());
auto const size = endPos - startPos;
return reader.CreateSubReader(startPos, size);
}
template <typename ReaderType>
std::unique_ptr<Reader> CreateCategorySubReader(ReaderType const & reader)
{
return CreateSubReader(reader, m_header.m_categoryOffset, m_header.m_bookmarksOffset);
}
template <typename ReaderType>
std::unique_ptr<Reader> CreateBookmarkSubReader(ReaderType const & reader)
{
return CreateSubReader(reader, m_header.m_bookmarksOffset, m_header.m_tracksOffset);
}
template <typename ReaderType>
std::unique_ptr<Reader> CreateTrackSubReader(ReaderType const & reader)
{
return CreateSubReader(reader, m_header.m_tracksOffset, m_header.m_compilationsOffset);
}
template <typename ReaderType>
std::unique_ptr<Reader> CreateCompilationsSubReader(ReaderType const & reader)
{
return CreateSubReader(reader, m_header.m_compilationsOffset, m_header.m_stringsOffset);
}
template <typename ReaderType>
std::unique_ptr<Reader> CreateStringsSubReader(ReaderType const & reader)
{
return CreateSubReader(reader, m_header.m_stringsOffset, m_header.m_eosOffset);
}
void ReadDeviceId(NonOwningReaderSource & source)
{
auto const sz = ReadVarUint<uint32_t>(source);
m_data.m_deviceId.resize(sz);
source.Read(&m_data.m_deviceId[0], sz);
}
void ReadServerId(NonOwningReaderSource & source)
{
auto const sz = ReadVarUint<uint32_t>(source);
m_data.m_serverId.resize(sz);
source.Read(&m_data.m_serverId[0], sz);
}
void ReadBitsCountInDouble(NonOwningReaderSource & source)
{
m_doubleBits = ReadPrimitiveFromSource<uint8_t>(source);
if (m_doubleBits == 0 || m_doubleBits > 32)
MYTHROW(DeserializeException, ("Incorrect double bits count: ", m_doubleBits));
}
template <typename FileDataType>
void DeserializeFileData(std::unique_ptr<Reader> & subReader, FileDataType & data)
{
// Keep in mind - deserialization/serialization works in two stages:
// - serialization/deserialization non-string members of structures;
// - serialization/deserialization string members of structures.
DeserializeCategory(subReader, data);
DeserializeBookmarks(subReader, data);
DeserializeTracks(subReader, data);
if constexpr (HasCompilationsData<FileDataType>::value)
DeserializeCompilations(subReader, data);
DeserializeStrings(subReader, data);
}
template <typename FileDataType>
void DeserializeCategory(std::unique_ptr<Reader> & subReader, FileDataType & data)
{
auto categorySubReader = CreateCategorySubReader(*subReader);
NonOwningReaderSource src(*categorySubReader);
CategoryDeserializerVisitor<decltype(src)> visitor(src, m_doubleBits);
visitor(data.m_categoryData);
}
template <typename FileDataType>
void DeserializeBookmarks(std::unique_ptr<Reader> & subReader, FileDataType & data)
{
auto bookmarkSubReader = CreateBookmarkSubReader(*subReader);
NonOwningReaderSource src(*bookmarkSubReader);
BookmarkDeserializerVisitor<decltype(src)> visitor(src, m_doubleBits);
visitor(data.m_bookmarksData);
}
void MigrateBookmarksV2(FileDataV3 & data)
{
for (auto & d : data.m_bookmarksData)
d.m_featureTypes.clear();
}
template <typename FileDataType>
void DeserializeTracks(std::unique_ptr<Reader> & subReader, FileDataType & data)
{
auto trackSubReader = CreateTrackSubReader(*subReader);
NonOwningReaderSource src(*trackSubReader);
BookmarkDeserializerVisitor<decltype(src)> visitor(src, m_doubleBits);
visitor(data.m_tracksData);
}
template <typename FileDataType>
void DeserializeCompilations(std::unique_ptr<Reader> & subReader, FileDataType & data)
{
auto compilationsSubReader = CreateCompilationsSubReader(*subReader);
NonOwningReaderSource src(*compilationsSubReader);
CategoryDeserializerVisitor<decltype(src)> visitor(src, m_doubleBits);
visitor(data.m_compilationsData);
}
template <typename FileDataType>
void DeserializeStrings(std::unique_ptr<Reader> & subReader, FileDataType & data)
{
auto textsSubReader = CreateStringsSubReader(*subReader);
coding::BlockedTextStorage<Reader> strings(*textsSubReader);
DeserializedStringCollector<Reader> collector(strings);
CollectorVisitor<decltype(collector)> visitor(collector);
data.Visit(visitor);
CollectorVisitor<decltype(collector)> clearVisitor(collector, true /* clear index */);
data.Visit(clearVisitor);
}
FileData & m_data;
Header m_header;
uint8_t m_doubleBits = 0;
bool m_initialized = false;
};
} // namespace binary
} // namespace kml

View file

@ -0,0 +1,88 @@
#pragma once
#include "kml/serdes_binary.hpp"
#include "kml/types.hpp"
#include "kml/types_v8.hpp"
#include "kml/visitors.hpp"
#include <string>
#include <vector>
namespace kml::binary
{
// This class generates KMB files in format V8.
// The only difference between V8 and V9 (Latest) is bookmarks structure.
class SerializerKmlV8 : public SerializerKml
{
public:
explicit SerializerKmlV8(FileData & data) : SerializerKml(data) {}
template <typename Sink>
void Serialize(Sink & sink)
{
// Write format version.
WriteToSink(sink, Version::V8);
// Write device id.
{
auto const sz = static_cast<uint32_t>(m_data.m_deviceId.size());
WriteVarUint(sink, sz);
sink.Write(m_data.m_deviceId.data(), sz);
}
// Write server id.
{
auto const sz = static_cast<uint32_t>(m_data.m_serverId.size());
WriteVarUint(sink, sz);
sink.Write(m_data.m_serverId.data(), sz);
}
// Write bits count in double number.
WriteToSink(sink, kDoubleBits);
auto const startPos = sink.Pos();
// Reserve place for the header.
Header header;
WriteZeroesToSink(sink, header.Size());
// Serialize category.
header.m_categoryOffset = sink.Pos() - startPos;
SerializeCategory(sink);
// Serialize bookmarks.
header.m_bookmarksOffset = sink.Pos() - startPos;
SerializeBookmarks(sink);
// Serialize tracks.
header.m_tracksOffset = sink.Pos() - startPos;
SerializeTracks(sink);
// Serialize compilations.
header.m_compilationsOffset = sink.Pos() - startPos;
SerializeCompilations(sink);
// Serialize strings.
header.m_stringsOffset = sink.Pos() - startPos;
SerializeStrings(sink);
// Fill header.
header.m_eosOffset = sink.Pos() - startPos;
sink.Seek(startPos);
header.Serialize(sink);
sink.Seek(startPos + header.m_eosOffset);
}
template <typename Sink>
void SerializeBookmarks(Sink & sink)
{
BookmarkSerializerVisitor<Sink> visitor(sink, kDoubleBits);
// Downgrade bookmark format from Latest to V8
std::vector<BookmarkDataV8> bookmarksDataV8;
bookmarksDataV8.reserve(m_data.m_bookmarksData.size());
for (BookmarkData & bm : m_data.m_bookmarksData)
bookmarksDataV8.push_back(BookmarkDataV8(bm));
visitor(bookmarksDataV8);
}
};
} // namespace kml::binary

View file

@ -0,0 +1,79 @@
#include "kml/serdes_common.hpp"
#include <sstream>
#include "base/string_utils.hpp"
#include "geometry/mercator.hpp"
namespace kml
{
std::string PointToString(m2::PointD const & org, char const separator)
{
double const lon = mercator::XToLon(org.x);
double const lat = mercator::YToLat(org.y);
std::ostringstream ss;
ss.precision(8);
ss << lon << separator << lat;
return ss.str();
}
std::string PointToLineString(geometry::PointWithAltitude const & pt)
{
char constexpr kSeparator = ',';
if (pt.GetAltitude() != geometry::kInvalidAltitude)
return PointToString(pt.GetPoint(), kSeparator) + kSeparator + strings::to_string(pt.GetAltitude());
return PointToString(pt.GetPoint(), kSeparator);
}
std::string PointToGxString(geometry::PointWithAltitude const & pt)
{
char constexpr kSeparator = ' ';
if (pt.GetAltitude() != geometry::kInvalidAltitude)
return PointToString(pt.GetPoint(), kSeparator) + kSeparator + strings::to_string(pt.GetAltitude());
return PointToString(pt.GetPoint(), kSeparator);
}
void SaveStringWithCDATA(Writer & writer, std::string s)
{
if (s.empty())
return;
// Expat loads XML 1.0 only. Sometimes users copy and paste bookmark descriptions or even names from the web.
// Rarely, in these copy-pasted texts, there are invalid XML1.0 symbols.
// See https://en.wikipedia.org/wiki/Valid_characters_in_XML
// A robust solution requires parsing invalid XML on loading (then users can restore "bad" XML files), see
// https://github.com/organicmaps/organicmaps/issues/3837
// When a robust solution is implemented, this workaround can be removed for better performance/battery.
//
// This solution is a simple ASCII-range check that does not check symbols from other unicode ranges
// (they will require a more complex and slower approach of converting UTF-8 string to unicode first).
// It should be enough for many cases, according to user reports and wrong characters in their data.
s.erase(std::remove_if(s.begin(), s.end(),
[](unsigned char c)
{
if (c >= 0x20 || c == 0x09 || c == 0x0a || c == 0x0d)
return false;
return true;
}),
s.end());
if (s.empty())
return;
// According to kml/xml spec, we need to escape special symbols with CDATA.
if (s.find_first_of("<&") != std::string::npos)
writer << "<![CDATA[" << s << "]]>";
else
writer << s;
}
std::string const * GetDefaultLanguage(LocalizableString const & lstr)
{
auto const find = lstr.find(kDefaultLang);
if (find != lstr.end())
return &find->second;
return nullptr;
}
} // namespace kml

View file

@ -0,0 +1,31 @@
#pragma once
#include "kml/type_utils.hpp"
#include "coding/string_utf8_multilang.hpp"
#include "coding/writer.hpp"
#include "geometry/point2d.hpp"
#include "geometry/point_with_altitude.hpp"
namespace kml
{
auto constexpr kDefaultLang = StringUtf8Multilang::kDefaultCode;
auto constexpr kDefaultTrackWidth = 5.0;
auto constexpr kDefaultTrackColor = 0x006ec7ff;
std::string PointToString(m2::PointD const & org, char const separator);
std::string PointToLineString(geometry::PointWithAltitude const & pt);
std::string PointToGxString(geometry::PointWithAltitude const & pt);
void SaveStringWithCDATA(Writer & writer, std::string s);
std::string const * GetDefaultLanguage(LocalizableString const & lstr);
std::string_view constexpr kIndent0{};
std::string_view constexpr kIndent2{" "};
std::string_view constexpr kIndent4{" "};
std::string_view constexpr kIndent6{" "};
std::string_view constexpr kIndent8{" "};
std::string_view constexpr kIndent10{" "};
} // namespace kml

610
libs/kml/serdes_gpx.cpp Normal file
View file

@ -0,0 +1,610 @@
#include "kml/serdes_gpx.hpp"
#include "kml/color_parser.hpp"
#include "kml/serdes_common.hpp"
#include "coding/hex.hpp"
#include "coding/point_coding.hpp"
#include "geometry/mercator.hpp"
#include "base/assert.hpp"
#include "base/string_utils.hpp"
namespace kml
{
namespace gpx
{
namespace
{
std::string_view constexpr kTrk = "trk";
std::string_view constexpr kTrkSeg = "trkseg";
std::string_view constexpr kRte = "rte";
std::string_view constexpr kTrkPt = "trkpt";
std::string_view constexpr kWpt = "wpt";
std::string_view constexpr kRtePt = "rtept";
std::string_view constexpr kName = "name";
std::string_view constexpr kColor = "color";
std::string_view constexpr kOsmandColor = "osmand:color";
std::string_view constexpr kGpx = "gpx";
std::string_view constexpr kGarminColor = "gpxx:DisplayColor";
std::string_view constexpr kDesc = "desc";
std::string_view constexpr kMetadata = "metadata";
std::string_view constexpr kEle = "ele";
std::string_view constexpr kCmt = "cmt";
std::string_view constexpr kTime = "time";
std::string_view constexpr kGpxHeader = R"(<?xml version="1.0"?>
<gpx version="1.1" creator="CoMaps" xmlns="http://www.topografix.com/GPX/1/1"
xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"
xmlns:gpx_style="http://www.topografix.com/GPX/gpx_style/0/2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 https://www.topografix.com/GPX/1/1/gpx.xsd http://www.topografix.com/GPX/gpx_style/0/2 https://www.topografix.com/GPX/gpx_style/0/2/gpx_style.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 https://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd">
)";
std::string_view constexpr kGpxFooter = "</gpx>";
int constexpr kInvalidColor = 0;
} // namespace
GpxParser::GpxParser(FileData & data)
: m_data{data}
, m_categoryData{&m_data.m_categoryData}
, m_globalColor{kInvalidColor}
{
ResetPoint();
}
void GpxParser::ResetPoint()
{
m_name.clear();
m_description.clear();
m_comment.clear();
m_org = {};
m_predefinedColor = PredefinedColor::None;
m_color = kInvalidColor;
m_customName.clear();
m_geometry.Clear();
m_geometryType = GEOMETRY_TYPE_UNKNOWN;
m_lat = 0.;
m_lon = 0.;
m_altitude = geometry::kInvalidAltitude;
m_timestamp = base::INVALID_TIME_STAMP;
}
bool GpxParser::MakeValid()
{
if (GEOMETRY_TYPE_POINT == m_geometryType)
{
m2::PointD const & pt = m_org.GetPoint();
if (mercator::ValidX(pt.x) && mercator::ValidY(pt.y))
{
// Set default name.
if (m_name.empty())
m_name = kml::PointToLineString(m_org);
if (m_color != kInvalidColor)
m_predefinedColor = MapPredefinedColor(m_color);
else
m_predefinedColor = PredefinedColor::Red;
return true;
}
return false;
}
else if (GEOMETRY_TYPE_LINE == m_geometryType)
{
return m_geometry.IsValid();
}
return false;
}
bool GpxParser::Push(std::string tag)
{
if (tag == gpx::kWpt)
m_geometryType = GEOMETRY_TYPE_POINT;
else if (tag == gpx::kTrkPt || tag == gpx::kRtePt)
m_geometryType = GEOMETRY_TYPE_LINE;
m_tags.emplace_back(std::move(tag));
return true;
}
bool GpxParser::IsValidCoordinatesPosition() const
{
std::string const & lastTag = GetTagFromEnd(0);
return lastTag == gpx::kWpt || (lastTag == gpx::kTrkPt && GetTagFromEnd(1) == gpx::kTrkSeg) ||
(lastTag == gpx::kRtePt && GetTagFromEnd(1) == gpx::kRte);
}
void GpxParser::AddAttr(std::string_view attr, char const * value)
{
if (IsValidCoordinatesPosition())
{
if (attr == "lat" && !strings::to_double(value, m_lat))
LOG(LERROR, ("Bad gpx latitude:", value));
else if (attr == "lon" && !strings::to_double(value, m_lon))
LOG(LERROR, ("Bad gpx longitude:", value));
}
}
std::string const & GpxParser::GetTagFromEnd(size_t n) const
{
ASSERT_LESS(n, m_tags.size(), ());
return m_tags[m_tags.size() - n - 1];
}
std::optional<uint32_t> GpxParser::ParseColorFromHexString(std::string_view colorStr)
{
auto const res = ParseHexColor(colorStr);
if (!res)
LOG(LWARNING, ("Invalid color value", colorStr));
return res;
}
void GpxParser::ParseColor(std::string_view colorStr)
{
if (auto const parsed = ParseColorFromHexString(colorStr))
m_color = *parsed;
}
// https://osmand.net/docs/technical/osmand-file-formats/osmand-gpx/. Supported colors:
// #AARRGGBB/#RRGGBB/AARRGGBB/RRGGBB
void GpxParser::ParseOsmandColor(std::string const & value)
{
auto const color = ParseColorFromHexString(value);
if (!color)
return;
if (m_tags.size() > 2 && GetTagFromEnd(2) == gpx::kGpx)
{
m_globalColor = *color;
for (auto & track : m_data.m_tracksData)
for (auto & layer : track.m_layers)
layer.m_color.m_rgba = m_globalColor;
}
else
m_color = *color;
}
void GpxParser::ParseGarminColor(std::string const & v)
{
auto const res = kml::ParseGarminColor(v);
if (!res)
{
LOG(LWARNING, ("Unsupported color value", v));
m_color = ToRGBA(255, 0, 0); // default to red
}
else
m_color = *res;
}
void GpxParser::CheckAndCorrectTimestamps()
{
ASSERT_EQUAL(m_line.size(), m_timestamps.size(), ());
size_t const numInvalid = std::count(m_timestamps.begin(), m_timestamps.end(), base::INVALID_TIME_STAMP);
if (numInvalid * 2 > m_timestamps.size())
{
// >50% invalid
m_timestamps.clear();
}
else if (numInvalid > 0)
{
// Find INVALID_TIME_STAMP ranges and interpolate them.
for (size_t i = 0; i < m_timestamps.size();)
{
if (m_timestamps[i] == base::INVALID_TIME_STAMP)
{
size_t j = i + 1;
for (; j < m_timestamps.size(); ++j)
{
if (m_timestamps[j] != base::INVALID_TIME_STAMP)
{
if (i == 0)
{
// Beginning range assign to the first valid timestamp.
while (i < j)
m_timestamps[i++] = m_timestamps[j];
}
else
{
// naive interpolation
auto const last = m_timestamps[i - 1];
auto count = j - i + 1;
double const delta = (m_timestamps[j] - last) / double(count);
for (size_t k = 1; k < count; ++k)
m_timestamps[i++] = last + k * delta;
}
break;
}
}
if (j == m_timestamps.size())
{
// Ending range assign to the last valid timestamp.
ASSERT(i > 0, ());
auto const last = m_timestamps[i - 1];
while (i < j)
m_timestamps[i++] = last;
}
i = j + 1;
}
else
++i;
}
}
}
void GpxParser::Pop(std::string_view tag)
{
ASSERT_EQUAL(m_tags.back(), tag, ());
if (tag == gpx::kTrkPt || tag == gpx::kRtePt)
{
m2::PointD const p = mercator::FromLatLon(m_lat, m_lon);
if (m_line.empty() || !AlmostEqualAbs(m_line.back().GetPoint(), p, kMwmPointAccuracy))
{
m_line.emplace_back(p, m_altitude);
m_timestamps.emplace_back(m_timestamp);
}
m_altitude = geometry::kInvalidAltitude;
m_timestamp = base::INVALID_TIME_STAMP;
}
else if (tag == gpx::kTrkSeg || tag == gpx::kRte)
{
if (m_line.size() > 1)
{
CheckAndCorrectTimestamps();
m_geometry.m_lines.push_back(std::move(m_line));
m_geometry.m_timestamps.push_back(std::move(m_timestamps));
}
// Clear segment (it may be incomplete).
m_line.clear();
m_timestamps.clear();
}
else if (tag == gpx::kWpt)
{
m_org.SetPoint(mercator::FromLatLon(m_lat, m_lon));
m_org.SetAltitude(m_altitude);
m_altitude = geometry::kInvalidAltitude;
}
if (tag == gpx::kRte || tag == gpx::kTrk || tag == gpx::kWpt)
{
if (MakeValid())
{
if (GEOMETRY_TYPE_POINT == m_geometryType)
{
BookmarkData data;
if (!m_name.empty())
data.m_name[kDefaultLang] = std::move(m_name);
if (!m_description.empty() || !m_comment.empty())
data.m_description[kDefaultLang] = BuildDescription();
data.m_color.m_predefinedColor = m_predefinedColor;
data.m_color.m_rgba = m_color;
data.m_point = m_org;
if (!m_customName.empty())
data.m_customName[kDefaultLang] = std::move(m_customName);
else if (!data.m_name.empty())
{
// Here we set custom name from 'name' field for KML-files exported from 3rd-party services.
data.m_customName = data.m_name;
}
m_data.m_bookmarksData.push_back(std::move(data));
}
else if (GEOMETRY_TYPE_LINE == m_geometryType)
{
#ifdef DEBUG
// Default gpx parser doesn't check points and timestamps count as the kml parser does.
for (size_t lineIndex = 0; lineIndex < m_geometry.m_lines.size(); ++lineIndex)
{
auto const pointsSize = m_geometry.m_lines[lineIndex].size();
auto const timestampsSize = m_geometry.m_timestamps[lineIndex].size();
ASSERT(!m_geometry.HasTimestampsFor(lineIndex) || pointsSize == timestampsSize, (pointsSize, timestampsSize));
}
#endif
TrackLayer layer;
layer.m_lineWidth = kml::kDefaultTrackWidth;
if (m_color != kInvalidColor)
layer.m_color.m_rgba = m_color;
else if (m_globalColor != kInvalidColor)
layer.m_color.m_rgba = m_globalColor;
else
layer.m_color.m_rgba = kml::kDefaultTrackColor;
TrackData data;
if (!m_name.empty())
data.m_name[kDefaultLang] = std::move(m_name);
if (!m_description.empty() || !m_comment.empty())
data.m_description[kDefaultLang] = BuildDescription();
data.m_layers.push_back(layer);
data.m_geometry = std::move(m_geometry);
m_data.m_tracksData.push_back(std::move(data));
}
}
ResetPoint();
}
if (tag == gpx::kMetadata)
{
/// @todo(KK): Process the <metadata><time> tag.
m_timestamp = base::INVALID_TIME_STAMP;
}
m_tags.pop_back();
}
void GpxParser::CharData(std::string & value)
{
strings::Trim(value);
size_t const count = m_tags.size();
if (count > 1 && !value.empty())
{
std::string const & currTag = m_tags[count - 1];
std::string const & prevTag = m_tags[count - 2];
if (currTag == gpx::kName)
ParseName(value, prevTag);
else if (currTag == gpx::kDesc)
ParseDescription(value, prevTag);
else if (currTag == gpx::kGarminColor)
ParseGarminColor(value);
else if (currTag == gpx::kOsmandColor)
ParseOsmandColor(value);
else if (currTag == gpx::kColor)
ParseColor(value);
else if (currTag == gpx::kEle)
ParseAltitude(value);
else if (currTag == gpx::kCmt)
m_comment = value;
else if (currTag == gpx::kTime)
ParseTimestamp(value);
}
}
void GpxParser::ParseDescription(std::string const & value, std::string const & prevTag)
{
if (prevTag == kWpt)
{
m_description = value;
}
else if (prevTag == kTrk || prevTag == kRte)
{
m_description = value;
if (m_categoryData->m_description[kDefaultLang].empty())
m_categoryData->m_description[kDefaultLang] = value;
}
else if (prevTag == kMetadata)
{
m_categoryData->m_description[kDefaultLang] = value;
}
}
void GpxParser::ParseName(std::string const & value, std::string const & prevTag)
{
if (prevTag == kWpt)
{
m_name = value;
}
else if (prevTag == kTrk || prevTag == kRte)
{
m_name = value;
if (m_categoryData->m_name[kDefaultLang].empty())
m_categoryData->m_name[kDefaultLang] = value;
}
else if (prevTag == kMetadata)
{
m_categoryData->m_name[kDefaultLang] = value;
}
}
void GpxParser::ParseAltitude(std::string const & value)
{
double rawAltitude;
if (strings::to_double(value, rawAltitude))
m_altitude = static_cast<geometry::Altitude>(round(rawAltitude));
else
m_altitude = geometry::kInvalidAltitude;
}
void GpxParser::ParseTimestamp(std::string const & value)
{
m_timestamp = base::StringToTimestamp(value);
}
std::string GpxParser::BuildDescription() const
{
if (m_description.empty())
return m_comment;
else if (m_comment.empty() || m_description == m_comment)
return m_description;
return m_description + "\n\n" + m_comment;
}
namespace
{
std::string CoordToString(double c)
{
std::ostringstream ss;
ss.precision(8);
ss << c;
return ss.str();
}
void SaveColorToRGB(Writer & writer, uint32_t rgba)
{
writer << NumToHex(static_cast<uint8_t>(rgba >> 24 & 0xFF)) << NumToHex(static_cast<uint8_t>((rgba >> 16) & 0xFF))
<< NumToHex(static_cast<uint8_t>((rgba >> 8) & 0xFF));
}
void SaveColorToARGB(Writer & writer, uint32_t rgba)
{
writer << NumToHex(static_cast<uint8_t>(rgba & 0xFF)) << NumToHex(static_cast<uint8_t>(rgba >> 24 & 0xFF))
<< NumToHex(static_cast<uint8_t>((rgba >> 16) & 0xFF)) << NumToHex(static_cast<uint8_t>((rgba >> 8) & 0xFF));
}
void SaveCategoryData(Writer & writer, CategoryData const & categoryData)
{
writer << "<metadata>\n";
if (auto const name = GetDefaultLanguage(categoryData.m_name))
{
writer << kIndent2 << "<name>";
SaveStringWithCDATA(writer, *name);
writer << "</name>\n";
}
if (auto const description = GetDefaultLanguage(categoryData.m_description))
{
writer << kIndent2 << "<desc>";
SaveStringWithCDATA(writer, *description);
writer << "</desc>\n";
}
writer << "</metadata>\n";
}
uint32_t BookmarkColor(BookmarkData const & bookmarkData)
{
auto const & [predefinedColor, rgba] = bookmarkData.m_color;
if (rgba != kInvalidColor)
return rgba;
if (predefinedColor != PredefinedColor::None && predefinedColor != PredefinedColor::Red)
return ColorFromPredefinedColor(predefinedColor).GetRGBA();
return kInvalidColor;
}
void SaveBookmarkData(Writer & writer, BookmarkData const & bookmarkData)
{
auto const [lat, lon] = mercator::ToLatLon(bookmarkData.m_point);
writer << "<wpt lat=\"" << CoordToString(lat) << "\" lon=\"" << CoordToString(lon) << "\">\n";
// If user customized the default bookmark name, it's saved in m_customName.
auto name = GetDefaultLanguage(bookmarkData.m_customName);
if (!name)
name = GetDefaultLanguage(bookmarkData.m_name); // Original POI name stored when bookmark was created.
if (name)
{
writer << kIndent2 << "<name>";
SaveStringWithCDATA(writer, *name);
writer << "</name>\n";
}
if (auto const description = GetDefaultLanguage(bookmarkData.m_description))
{
writer << kIndent2 << "<desc>";
SaveStringWithCDATA(writer, *description);
writer << "</desc>\n";
}
if (auto const color = BookmarkColor(bookmarkData); color != kInvalidColor)
{
writer << kIndent2 << "<extensions>\n";
writer << kIndent4 << "<xsi:gpx><color>#";
SaveColorToARGB(writer, color);
writer << "</color></xsi:gpx>\n";
writer << kIndent2 << "</extensions>\n";
}
writer << "</wpt>\n";
}
bool TrackHasAltitudes(TrackData const & trackData)
{
auto const & lines = trackData.m_geometry.m_lines;
if (lines.empty() || lines.front().empty())
return false;
auto const altitude = lines.front().front().GetAltitude();
return altitude != geometry::kDefaultAltitudeMeters && altitude != geometry::kInvalidAltitude;
}
uint32_t TrackColor(TrackData const & trackData)
{
if (trackData.m_layers.empty())
return kDefaultTrackColor;
return trackData.m_layers.front().m_color.m_rgba;
}
void SaveTrackData(Writer & writer, TrackData const & trackData)
{
writer << "<trk>\n";
auto name = GetDefaultLanguage(trackData.m_name);
if (name)
{
writer << kIndent2 << "<name>";
SaveStringWithCDATA(writer, *name);
writer << "</name>\n";
}
if (auto const description = GetDefaultLanguage(trackData.m_description))
{
writer << kIndent2 << "<desc>";
SaveStringWithCDATA(writer, *description);
writer << "</desc>\n";
}
if (auto const color = TrackColor(trackData); color != kDefaultTrackColor)
{
writer << kIndent2 << "<extensions>\n";
writer << kIndent4 << "<gpxx:TrackExtension><gpxx:DisplayColor>";
writer << kml::MapGarminColor(color);
writer << "</gpxx:DisplayColor></gpxx:TrackExtension>\n";
writer << kIndent4 << "<gpx_style:line><gpx_style:color>";
SaveColorToRGB(writer, color);
writer << "</gpx_style:color></gpx_style:line>\n";
writer << kIndent4 << "<xsi:gpx><color>#";
SaveColorToARGB(writer, color);
writer << "</color></xsi:gpx>\n";
writer << kIndent2 << "</extensions>\n";
}
bool const trackHasAltitude = TrackHasAltitudes(trackData);
auto const & geom = trackData.m_geometry;
for (size_t lineIndex = 0; lineIndex < geom.m_lines.size(); ++lineIndex)
{
auto const & line = geom.m_lines[lineIndex];
auto const & timestampsForLine = geom.m_timestamps[lineIndex];
auto const lineHasTimestamps = geom.HasTimestampsFor(lineIndex);
if (lineHasTimestamps)
CHECK_EQUAL(line.size(), timestampsForLine.size(), ());
writer << kIndent2 << "<trkseg>\n";
for (size_t pointIndex = 0; pointIndex < line.size(); ++pointIndex)
{
auto const & point = line[pointIndex];
auto const [lat, lon] = mercator::ToLatLon(point);
writer << kIndent4 << "<trkpt lat=\"" << CoordToString(lat) << "\" lon=\"" << CoordToString(lon) << "\">\n";
if (trackHasAltitude)
writer << kIndent6 << "<ele>" << CoordToString(point.GetAltitude()) << "</ele>\n";
if (lineHasTimestamps)
writer << kIndent6 << "<time>" << base::SecondsSinceEpochToString(timestampsForLine[pointIndex]) << "</time>\n";
writer << kIndent4 << "</trkpt>\n";
}
writer << kIndent2 << "</trkseg>\n";
}
writer << "</trk>\n";
}
} // namespace
void GpxWriter::Write(FileData const & fileData)
{
m_writer << kGpxHeader;
SaveCategoryData(m_writer, fileData.m_categoryData);
for (auto const & bookmarkData : fileData.m_bookmarksData)
SaveBookmarkData(m_writer, bookmarkData);
for (auto const & trackData : fileData.m_tracksData)
SaveTrackData(m_writer, trackData);
m_writer << kGpxFooter;
}
} // namespace gpx
DeserializerGpx::DeserializerGpx(FileData & fileData) : m_fileData(fileData)
{
m_fileData = {};
}
} // namespace kml

138
libs/kml/serdes_gpx.hpp Normal file
View file

@ -0,0 +1,138 @@
#pragma once
#include "kml/types.hpp"
#include "coding/parse_xml.hpp"
#include "coding/reader.hpp"
#include "coding/writer.hpp"
#include "geometry/point_with_altitude.hpp"
#include "base/exception.hpp"
#include <string>
namespace kml
{
namespace gpx
{
class GpxWriter
{
public:
DECLARE_EXCEPTION(WriteGpxException, RootException);
explicit GpxWriter(Writer & writer) : m_writer(writer) {}
void Write(FileData const & fileData);
private:
Writer & m_writer;
};
class SerializerGpx
{
public:
DECLARE_EXCEPTION(SerializeException, RootException);
explicit SerializerGpx(FileData const & fileData) : m_fileData(fileData) {}
template <typename Sink>
void Serialize(Sink & sink)
{
GpxWriter gpxWriter(sink);
gpxWriter.Write(m_fileData);
}
private:
FileData const & m_fileData;
};
class GpxParser
{
public:
explicit GpxParser(FileData & data);
bool Push(std::string name);
void AddAttr(std::string_view attr, char const * value);
std::string const & GetTagFromEnd(size_t n) const;
void Pop(std::string_view tag);
void CharData(std::string & value);
static std::optional<uint32_t> ParseColorFromHexString(std::string_view colorStr);
private:
enum GeometryType
{
GEOMETRY_TYPE_UNKNOWN,
GEOMETRY_TYPE_POINT,
GEOMETRY_TYPE_LINE
};
void ResetPoint();
bool MakeValid();
void ParseColor(std::string_view colorStr);
void ParseGarminColor(std::string const & value);
void ParseOsmandColor(std::string const & value);
bool IsValidCoordinatesPosition() const;
void CheckAndCorrectTimestamps();
FileData & m_data;
CategoryData m_compilationData;
CategoryData * m_categoryData; // never null
std::vector<std::string> m_tags;
GeometryType m_geometryType;
MultiGeometry m_geometry;
uint32_t m_color;
uint32_t m_globalColor; // To support OSMAnd extensions with single color per GPX file
std::string m_name;
std::string m_description;
std::string m_comment;
PredefinedColor m_predefinedColor;
geometry::PointWithAltitude m_org;
double m_lat;
double m_lon;
geometry::Altitude m_altitude;
time_t m_timestamp;
MultiGeometry::LineT m_line;
MultiGeometry::TimeT m_timestamps;
std::string m_customName;
void ParseName(std::string const & value, std::string const & prevTag);
void ParseDescription(std::string const & value, std::string const & prevTag);
void ParseAltitude(std::string const & value);
void ParseTimestamp(std::string const & value);
std::string BuildDescription() const;
};
} // namespace gpx
class DeserializerGpx
{
public:
DECLARE_EXCEPTION(DeserializeException, RootException);
explicit DeserializerGpx(FileData & fileData);
template <typename ReaderType>
void Deserialize(ReaderType const & reader)
{
NonOwningReaderSource src(reader);
gpx::GpxParser parser(m_fileData);
if (!ParseXML(src, parser, true))
{
// Print corrupted GPX file for debug and restore purposes.
std::string gpxText;
reader.ReadAsString(gpxText);
if (!gpxText.empty() && gpxText[0] == '<')
LOG(LWARNING, (gpxText));
MYTHROW(DeserializeException, ("Could not parse GPX."));
}
}
private:
FileData & m_fileData;
};
} // namespace kml

80
libs/kml/type_utils.cpp Normal file
View file

@ -0,0 +1,80 @@
#include "kml/type_utils.hpp"
#include "kml/types.hpp"
#include "indexer/classificator.hpp"
#include "indexer/feature_utils.hpp"
#include "platform/localization.hpp"
#include "platform/preferred_languages.hpp"
#include "coding/string_utf8_multilang.hpp"
namespace kml
{
bool IsEqual(m2::PointD const & lhs, m2::PointD const & rhs)
{
return lhs.EqualDxDy(rhs, kMwmPointAccuracy);
}
bool IsEqual(geometry::PointWithAltitude const & lhs, geometry::PointWithAltitude const & rhs)
{
return AlmostEqualAbs(lhs, rhs, kMwmPointAccuracy);
}
std::string GetPreferredBookmarkStr(LocalizableString const & name, std::string const & languageNorm)
{
if (name.size() == 1)
return name.begin()->second;
/// @todo Complicated logic here when transforming LocalizableString -> StringUtf8Multilang to call GetPreferredName.
StringUtf8Multilang nameMultilang;
for (auto const & pair : name)
nameMultilang.AddString(pair.first, pair.second);
auto const deviceLang = StringUtf8Multilang::GetLangIndex(languageNorm);
std::string_view preferredName;
if (feature::GetPreferredName(nameMultilang, deviceLang, preferredName))
return std::string(preferredName);
return {};
}
std::string GetPreferredBookmarkStr(LocalizableString const & name, feature::RegionData const & regionData,
std::string const & languageNorm)
{
if (name.size() == 1)
return name.begin()->second;
/// @todo Complicated logic here when transforming LocalizableString -> StringUtf8Multilang to call GetPreferredName.
StringUtf8Multilang nameMultilang;
for (auto const & pair : name)
nameMultilang.AddString(pair.first, pair.second);
feature::NameParamsOut out;
feature::GetReadableName({nameMultilang, regionData, languageNorm, false /* allowTranslit */}, out);
return std::string(out.primary);
}
std::string GetLocalizedFeatureType(std::vector<uint32_t> const & types)
{
if (types.empty())
return {};
auto const & c = classif();
auto const type = c.GetTypeForIndex(types.front());
return platform::GetLocalizedTypeName(c.GetReadableObjectName(type));
}
std::string GetPreferredBookmarkName(BookmarkData const & bmData, std::string_view languageOrig)
{
auto const languageNorm = languages::Normalize(languageOrig);
std::string name = GetPreferredBookmarkStr(bmData.m_customName, languageNorm);
if (name.empty())
name = GetPreferredBookmarkStr(bmData.m_name, languageNorm);
if (name.empty())
name = GetLocalizedFeatureType(bmData.m_featureTypes);
return name;
}
} // namespace kml

141
libs/kml/type_utils.hpp Normal file
View file

@ -0,0 +1,141 @@
#pragma once
#include "geometry/point_with_altitude.hpp"
#include <chrono>
#include <limits>
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
namespace feature
{
class RegionData;
}
namespace kml
{
using TimestampClock = std::chrono::system_clock;
using Timestamp = std::chrono::time_point<TimestampClock>;
class TimestampMillis : public Timestamp
{
public:
TimestampMillis() = default;
explicit TimestampMillis(Timestamp const & ts) : Timestamp{ts} {}
TimestampMillis & operator=(Timestamp const & ts)
{
if (this != &ts)
Timestamp::operator=(ts);
return *this;
}
};
using LocalizableString = std::unordered_map<int8_t, std::string>;
using LocalizableStringSubIndex = std::map<int8_t, uint32_t>;
using LocalizableStringIndex = std::vector<LocalizableStringSubIndex>;
using Properties = std::map<std::string, std::string>;
using MarkGroupId = uint64_t;
using MarkId = uint64_t;
using TrackId = uint64_t;
using LocalId = uint8_t;
using CompilationId = uint64_t;
using MarkIdCollection = std::vector<MarkId>;
using TrackIdCollection = std::vector<TrackId>;
using MarkIdSet = std::set<MarkId, std::greater<MarkId>>;
using TrackIdSet = std::set<TrackId>;
using GroupIdCollection = std::vector<MarkGroupId>;
using GroupIdSet = std::set<MarkGroupId>;
MarkGroupId constexpr kInvalidMarkGroupId = std::numeric_limits<MarkGroupId>::max();
MarkId constexpr kInvalidMarkId = std::numeric_limits<MarkId>::max();
MarkId constexpr kDebugMarkId = kInvalidMarkId - 1;
TrackId constexpr kInvalidTrackId = std::numeric_limits<TrackId>::max();
CompilationId constexpr kInvalidCompilationId = std::numeric_limits<CompilationId>::max();
inline uint64_t ToSecondsSinceEpoch(Timestamp const & time)
{
auto const s = std::chrono::duration_cast<std::chrono::seconds>(time.time_since_epoch());
return static_cast<uint64_t>(s.count());
}
inline Timestamp FromSecondsSinceEpoch(uint64_t seconds)
{
return Timestamp(std::chrono::seconds(seconds));
}
inline bool IsEqual(Timestamp const & ts1, Timestamp const & ts2)
{
return ToSecondsSinceEpoch(ts1) == ToSecondsSinceEpoch(ts2);
}
uint32_t constexpr kEmptyStringId = 0;
double constexpr kMinLineWidth = 0.0;
double constexpr kMaxLineWidth = 100.0;
double constexpr kMinRating = 0.0;
double constexpr kMaxRating = 10.0;
uint8_t constexpr kDoubleBits = 30;
int8_t constexpr kDefaultLangCode = 0;
inline std::string GetDefaultStr(LocalizableString const & str)
{
return (str.empty() || str.find(kDefaultLangCode) == str.end()) ? "" : str.at(kDefaultLangCode);
}
inline void SetDefaultStr(LocalizableString & localizableStr, std::string const & str)
{
if (str.empty())
{
localizableStr.erase(kDefaultLangCode);
return;
}
localizableStr[kDefaultLangCode] = str;
}
bool IsEqual(m2::PointD const & lhs, m2::PointD const & rhs);
bool IsEqual(geometry::PointWithAltitude const & lhs, geometry::PointWithAltitude const & rhs);
template <class T>
bool IsEqual(std::vector<T> const & lhs, std::vector<T> const & rhs)
{
if (lhs.size() != rhs.size())
return false;
for (size_t i = 0; i < lhs.size(); ++i)
if (!IsEqual(lhs[i], rhs[i]))
return false;
return true;
}
struct BookmarkData;
std::string GetPreferredBookmarkName(BookmarkData const & bmData, std::string_view languageOrig);
std::string GetPreferredBookmarkStr(LocalizableString const & name, std::string const & languageNorm);
std::string GetPreferredBookmarkStr(LocalizableString const & name, feature::RegionData const & regionData,
std::string const & languageNorm);
std::string GetLocalizedFeatureType(std::vector<uint32_t> const & types);
#define DECLARE_COLLECTABLE(IndexType, ...) \
IndexType m_collectionIndex; \
template <typename Collector> \
void Collect(Collector & collector) \
{ \
collector.Collect(m_collectionIndex, __VA_ARGS__); \
} \
void ClearCollectionIndex() \
{ \
m_collectionIndex.clear(); \
}
#define VISITOR_COLLECTABLE visitor(m_collectionIndex, "collectionIndex")
#define SKIP_VISITING(Type) \
void operator()(Type, char const * = nullptr) {}
} // namespace kml

113
libs/kml/types.cpp Normal file
View file

@ -0,0 +1,113 @@
#include "kml/types.hpp"
#include "kml/minzoom_quadtree.hpp"
#include "base/macros.hpp"
#include "base/stl_helpers.hpp"
#include "base/string_utils.hpp"
#include <random>
namespace kml
{
void SetBookmarksMinZoom(FileData & fileData, double countPerTile, int maxZoom)
{
using ValueType = std::pair<BookmarkData *, int /* score */>;
auto const scoreLess = base::LessBy(&ValueType::second);
MinZoomQuadtree<ValueType, decltype(scoreLess)> minZoomQuadtree{scoreLess};
for (auto & bm : fileData.m_bookmarksData)
{
auto const & properties = bm.m_properties;
int score = 0;
if (auto const s = properties.find("score"); s != properties.end())
UNUSED_VALUE(strings::to_int(s->second, score));
minZoomQuadtree.Add(bm.m_point, ValueType{&bm, score});
}
auto const setMinZoom = [](ValueType & valueType, int minZoom) { valueType.first->m_minZoom = minZoom; };
minZoomQuadtree.SetMinZoom(countPerTile, maxZoom, setMinZoom);
}
bool MultiGeometry::IsValid() const
{
ASSERT_EQUAL(m_lines.size(), m_timestamps.size(), ());
return !m_lines.empty();
}
void MultiGeometry::Clear()
{
m_lines.clear();
m_timestamps.clear();
}
bool MultiGeometry::HasTimestamps() const
{
if (m_timestamps.empty())
return false;
for (auto const & timestamp : m_timestamps)
if (!timestamp.empty())
return true;
return false;
}
bool MultiGeometry::HasTimestampsFor(size_t lineIndex) const
{
ASSERT(!m_timestamps.empty(), ());
ASSERT_EQUAL(m_lines.size(), m_timestamps.size(), ());
return !m_timestamps[lineIndex].empty();
}
size_t MultiGeometry::GetNumberOfLinesWithouTimestamps() const
{
return m_lines.size() - GetNumberOfLinesWithTimestamps();
}
size_t MultiGeometry::GetNumberOfLinesWithTimestamps() const
{
ASSERT_EQUAL(m_lines.size(), m_timestamps.size(), ());
size_t size = 0;
for (auto const & timestamp : m_timestamps)
if (!timestamp.empty())
++size;
return size;
}
void MultiGeometry::FromPoints(std::vector<m2::PointD> const & points)
{
LineT line;
for (auto const & pt : points)
line.emplace_back(pt);
ASSERT(line.size() > 1, ());
m_lines.push_back(std::move(line));
}
void MultiGeometry::AddLine(std::initializer_list<geometry::PointWithAltitude> lst)
{
m_lines.emplace_back();
m_lines.back().assign(lst);
}
void MultiGeometry::AddTimestamps(std::initializer_list<double> lst)
{
m_timestamps.emplace_back();
m_timestamps.back().assign(lst);
}
MultiGeometry mergeGeometry(std::vector<MultiGeometry> && aGeometries)
{
MultiGeometry merged;
for (auto && geometry : aGeometries)
for (auto && line : geometry.m_lines)
merged.m_lines.push_back(std::move(line));
return merged;
}
kml::PredefinedColor GetRandomPredefinedColor()
{
// Simple time-based seed instead of random_device is enough.
static std::mt19937 gen(static_cast<uint8_t>(std::chrono::system_clock::now().time_since_epoch().count()));
static std::uniform_int_distribution<> distr(1, static_cast<uint8_t>(PredefinedColor::Count) - 1);
return static_cast<PredefinedColor>(distr(gen));
}
} // namespace kml

582
libs/kml/types.hpp Normal file
View file

@ -0,0 +1,582 @@
#pragma once
#include "kml/type_utils.hpp"
#include "coding/point_coding.hpp"
#include "base/assert.hpp"
#include "base/internal/message.hpp" // DebugPrint(Timestamp)
#include "base/stl_helpers.hpp"
#include "base/visitor.hpp"
#include "drape/color.hpp"
#include <string>
#include <vector>
namespace kml
{
enum class PredefinedColor : uint8_t
{
None = 0,
Red,
Blue,
Purple,
Yellow,
Pink,
Brown,
Green,
Orange,
// Extended colors.
DeepPurple,
LightBlue,
Cyan,
Teal,
Lime,
DeepOrange,
Gray,
BlueGray,
Count
};
std::array constexpr kOrderedPredefinedColors = {
// clang-format off
PredefinedColor::None,
PredefinedColor::Red,
PredefinedColor::Pink,
PredefinedColor::Purple,
PredefinedColor::DeepPurple,
PredefinedColor::Blue,
PredefinedColor::LightBlue,
PredefinedColor::Cyan,
PredefinedColor::Teal,
PredefinedColor::Green,
PredefinedColor::Lime,
PredefinedColor::Yellow,
PredefinedColor::Orange,
PredefinedColor::DeepOrange,
PredefinedColor::Brown,
PredefinedColor::Gray,
PredefinedColor::BlueGray
// clang-format on
};
static_assert(kOrderedPredefinedColors.size() == static_cast<size_t>(PredefinedColor::Count),
"kOrderedPredefinedColors size must match PredefinedColor::Count");
static_assert(base::HasUniqueElements(kOrderedPredefinedColors), "All values must be unique");
/**
* @brief Maps PredefinedColor to its index in kOrderedPredefinedColors.
*
* @code
* kOrderedPredefinedColors[kColorIndexMap[base::E2I(PredefinedColor::Red)]] == PredefinedColor::Red
* @endcode
*/
std::array constexpr kColorIndexMap = [] consteval
{
std::array<int, static_cast<std::size_t>(PredefinedColor::Count)> map{};
for (std::size_t i = 0; i < kOrderedPredefinedColors.size(); ++i)
map[static_cast<std::size_t>(kOrderedPredefinedColors[i])] = static_cast<int>(i);
return map;
}();
inline std::string DebugPrint(PredefinedColor color)
{
switch (color)
{
using enum kml::PredefinedColor;
case None: return "None";
case Red: return "Red";
case Blue: return "Blue";
case Purple: return "Purple";
case Yellow: return "Yellow";
case Pink: return "Pink";
case Brown: return "Brown";
case Green: return "Green";
case Orange: return "Orange";
case DeepPurple: return "DeepPurple";
case LightBlue: return "LightBlue";
case Cyan: return "Cyan";
case Teal: return "Teal";
case Lime: return "Lime";
case DeepOrange: return "DeepOrange";
case Gray: return "Gray";
case BlueGray: return "BlueGray";
case Count: return {};
}
UNREACHABLE();
}
inline dp::Color ColorFromPredefinedColor(PredefinedColor color)
{
switch (color)
{
using enum kml::PredefinedColor;
case Red: return dp::Color(229, 27, 35, 255);
case Pink: return dp::Color(255, 65, 130, 255);
case Purple: return dp::Color(155, 36, 178, 255);
case DeepPurple: return dp::Color(102, 57, 191, 255);
case Blue: return dp::Color(0, 102, 204, 255);
case LightBlue: return dp::Color(36, 156, 242, 255);
case Cyan: return dp::Color(20, 190, 205, 255);
case Teal: return dp::Color(0, 165, 140, 255);
case Green: return dp::Color(60, 140, 60, 255);
case Lime: return dp::Color(147, 191, 57, 255);
case Yellow: return dp::Color(255, 200, 0, 255);
case Orange: return dp::Color(255, 150, 0, 255);
case DeepOrange: return dp::Color(240, 100, 50, 255);
case Brown: return dp::Color(128, 70, 51, 255);
case Gray: return dp::Color(115, 115, 115, 255);
case BlueGray: return dp::Color(89, 115, 128, 255);
case None:
case Count: return ColorFromPredefinedColor(kml::PredefinedColor::Red);
}
UNREACHABLE();
}
kml::PredefinedColor GetRandomPredefinedColor();
enum class AccessRules : uint8_t
{
// Do not change the order because of binary serialization.
Local = 0,
Public,
DirectLink,
P2P,
Paid,
AuthorOnly,
Count
};
inline std::string DebugPrint(AccessRules accessRules)
{
switch (accessRules)
{
using enum kml::AccessRules;
case Local: return "Local";
case Public: return "Public";
case DirectLink: return "DirectLink";
case P2P: return "P2P";
case Paid: return "Paid";
case AuthorOnly: return "AuthorOnly";
case Count: return {};
}
UNREACHABLE();
}
enum class CompilationType : uint8_t
{
// Do not change the order because of binary serialization.
Category = 0,
Collection,
Day,
};
inline std::string DebugPrint(CompilationType compilationType)
{
switch (compilationType)
{
using enum kml::CompilationType;
case Category: return "Category";
case Collection: return "Collection";
case Day: return "Day";
}
UNREACHABLE();
}
enum class BookmarkIcon : uint16_t
{
None = 0,
Hotel,
Animals,
Buddhism,
Building,
Christianity,
Entertainment,
Exchange,
Food,
Gas,
Judaism,
Medicine,
Mountain,
Museum,
Islam,
Park,
Parking,
Shop,
Sights,
Swim,
Water,
// Extended icons.
Bar,
Transport,
Viewpoint,
Sport,
Pub,
Art,
Bank,
Cafe,
Pharmacy,
Stadium,
Theatre,
Information,
ChargingStation,
BicycleParking,
BicycleParkingCovered,
BicycleRental,
FastFood,
Count
};
inline std::string ToString(BookmarkIcon icon)
{
switch (icon)
{
using enum kml::BookmarkIcon;
case None: return "None";
case Hotel: return "Hotel";
case Animals: return "Animals";
case Buddhism: return "Buddhism";
case Building: return "Building";
case Christianity: return "Christianity";
case Entertainment: return "Entertainment";
case Exchange: return "Exchange";
case Food: return "Food";
case Gas: return "Gas";
case Judaism: return "Judaism";
case Medicine: return "Medicine";
case Mountain: return "Mountain";
case Museum: return "Museum";
case Islam: return "Islam";
case Park: return "Park";
case Parking: return "Parking";
case Shop: return "Shop";
case Sights: return "Sights";
case Swim: return "Swim";
case Water: return "Water";
case Bar: return "Bar";
case Transport: return "Transport";
case Viewpoint: return "Viewpoint";
case Sport: return "Sport";
case Pub: return "Pub";
case Art: return "Art";
case Bank: return "Bank";
case Cafe: return "Cafe";
case Pharmacy: return "Pharmacy";
case Stadium: return "Stadium";
case Theatre: return "Theatre";
case Information: return "Information";
case ChargingStation: return "ChargingStation";
case BicycleParking: return "BicycleParking";
case BicycleParkingCovered: return "BicycleParkingCovered";
case BicycleRental: return "BicycleRental";
case FastFood: return "FastFood";
case Count: return {};
}
UNREACHABLE();
}
// Note: any changes in binary format of this structure
// will affect previous kmb versions, where this structure was used.
struct ColorData
{
DECLARE_VISITOR_AND_DEBUG_PRINT(ColorData, visitor(m_predefinedColor, "predefinedColor"), visitor(m_rgba, "rgba"))
bool operator==(ColorData const & data) const
{
return m_predefinedColor == data.m_predefinedColor && m_rgba == data.m_rgba;
}
bool operator!=(ColorData const & data) const { return !operator==(data); }
// Predefined color.
PredefinedColor m_predefinedColor = PredefinedColor::None;
// Color in RGBA format.
uint32_t m_rgba = 0;
};
// This structure is used in FileDataV6 because
// its binary format is the same as in kmb version 6.
// Make a copy of this structure in a file types_v<n>.hpp
// in case of any changes of its binary format.
struct BookmarkData
{
DECLARE_VISITOR_AND_DEBUG_PRINT(BookmarkData, visitor(m_id, "id"), visitor(m_name, "name"),
visitor(m_description, "description"), visitor(m_featureTypes, "featureTypes"),
visitor(m_customName, "customName"), visitor(m_color, "color"),
visitor(m_icon, "icon"), visitor(m_viewportScale, "viewportScale"),
visitor(m_timestamp, "timestamp"), visitor(m_point, "point"),
visitor(m_boundTracks, "boundTracks"), visitor(m_visible, "visible"),
visitor(m_nearestToponym, "nearestToponym"), visitor(m_minZoom, "minZoom"),
visitor(m_compilations, "compilations"), visitor(m_properties, "properties"),
VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_description, m_customName, m_nearestToponym, m_properties)
bool operator==(BookmarkData const & data) const
{
return m_id == data.m_id && m_name == data.m_name && m_description == data.m_description &&
m_color == data.m_color && m_icon == data.m_icon && m_viewportScale == data.m_viewportScale &&
IsEqual(m_timestamp, data.m_timestamp) && m_point.EqualDxDy(data.m_point, kMwmPointAccuracy) &&
m_featureTypes == data.m_featureTypes && m_customName == data.m_customName &&
m_boundTracks == data.m_boundTracks && m_visible == data.m_visible &&
m_nearestToponym == data.m_nearestToponym && m_minZoom == data.m_minZoom &&
m_compilations == data.m_compilations && m_properties == data.m_properties;
}
bool operator!=(BookmarkData const & data) const { return !operator==(data); }
// Unique id (it will not be serialized in text files).
MarkId m_id = kInvalidMarkId;
// Bookmark's name.
LocalizableString m_name;
// Bookmark's description.
LocalizableString m_description;
// Bound feature's types: type indices sorted by importance, the most
// important one goes first.
std::vector<uint32_t> m_featureTypes;
// Custom bookmark's name.
LocalizableString m_customName;
// Bookmark's color.
ColorData m_color;
// Bookmark's icon.
BookmarkIcon m_icon = BookmarkIcon::None;
// Viewport scale. 0 is a default value (no scale set).
uint8_t m_viewportScale = 0;
// Creation timestamp.
Timestamp m_timestamp = {};
// Coordinates in mercator.
m2::PointD m_point;
// Bound tracks (vector contains local track ids).
std::vector<LocalId> m_boundTracks;
// Visibility.
bool m_visible = true;
// Nearest toponym.
std::string m_nearestToponym;
// Minimal zoom when bookmark is visible.
int m_minZoom = 1;
// List of compilationIds.
std::vector<CompilationId> m_compilations;
// Key-value properties.
Properties m_properties;
};
// Note: any changes in binary format of this structure
// will affect previous kmb versions, where this structure was used.
struct TrackLayer
{
DECLARE_VISITOR_AND_DEBUG_PRINT(TrackLayer, visitor(m_lineWidth, "lineWidth"), visitor(m_color, "color"))
bool operator==(TrackLayer const & layer) const
{
double constexpr kEps = 1e-5;
return m_color == layer.m_color && fabs(m_lineWidth - layer.m_lineWidth) < kEps;
}
bool operator!=(TrackLayer const & layer) const { return !operator==(layer); }
// Line width in pixels. Valid range is [kMinLineWidth; kMaxLineWidth].
double m_lineWidth = 5.0;
// Layer's color.
ColorData m_color;
};
struct MultiGeometry
{
using LineT = std::vector<geometry::PointWithAltitude>;
using TimeT = std::vector<double>;
std::vector<LineT> m_lines;
std::vector<TimeT> m_timestamps;
void Clear();
bool IsValid() const;
bool operator==(MultiGeometry const & rhs) const
{
return IsEqual(m_lines, rhs.m_lines) && m_timestamps == rhs.m_timestamps;
}
friend std::string DebugPrint(MultiGeometry const & geom)
{
auto str = ::DebugPrint(geom.m_lines);
if (geom.HasTimestamps())
str.append(" ").append(::DebugPrint(geom.m_timestamps));
return str;
}
void FromPoints(std::vector<m2::PointD> const & points);
/// This method should be used for tests only.
void AddLine(std::initializer_list<geometry::PointWithAltitude> lst);
/// This method should be used for tests only.
void AddTimestamps(std::initializer_list<double> lst);
bool HasTimestamps() const;
bool HasTimestampsFor(size_t lineIndex) const;
size_t GetNumberOfLinesWithouTimestamps() const;
size_t GetNumberOfLinesWithTimestamps() const;
};
struct TrackData
{
DECLARE_VISITOR_AND_DEBUG_PRINT(TrackData, visitor(m_id, "id"), visitor(m_localId, "localId"),
visitor(m_name, "name"), visitor(m_description, "description"),
visitor(m_layers, "layers"), visitor(m_timestamp, "timestamp"),
visitor(m_geometry, "geometry"), visitor(m_visible, "visible"),
visitor(m_nearestToponyms, "nearestToponyms"), visitor(m_properties, "properties"),
VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_description, m_nearestToponyms, m_properties)
bool operator==(TrackData const & data) const
{
return m_id == data.m_id && m_localId == data.m_localId && m_name == data.m_name &&
m_description == data.m_description && m_layers == data.m_layers && IsEqual(m_timestamp, data.m_timestamp) &&
m_geometry == data.m_geometry && m_visible == data.m_visible &&
m_nearestToponyms == data.m_nearestToponyms && m_properties == data.m_properties;
}
bool operator!=(TrackData const & data) const { return !operator==(data); }
// Unique id (it will not be serialized in text files).
TrackId m_id = kInvalidTrackId;
// Local track id.
LocalId m_localId = 0;
// Track's name.
LocalizableString m_name;
// Track's description.
LocalizableString m_description;
// Layers.
std::vector<TrackLayer> m_layers;
// Creation timestamp.
Timestamp m_timestamp = {};
MultiGeometry m_geometry;
// Visibility.
bool m_visible = true;
// Nearest toponyms.
std::vector<std::string> m_nearestToponyms;
// Key-value properties.
Properties m_properties;
};
// This structure is used in FileDataV6 because
// its binary format is the same as in kmb version 6.
// Make a copy of this structure in a file types_v<n>.hpp
// in case of any changes of its binary format.
struct CategoryData
{
DECLARE_VISITOR_AND_DEBUG_PRINT(CategoryData, visitor(m_id, "id"), visitor(m_compilationId, "compilationId"),
visitor(m_type, "type"), visitor(m_name, "name"), visitor(m_imageUrl, "imageUrl"),
visitor(m_annotation, "annotation"), visitor(m_description, "description"),
visitor(m_visible, "visible"), visitor(m_authorName, "authorName"),
visitor(m_authorId, "authorId"), visitor(m_rating, "rating"),
visitor(m_reviewsNumber, "reviewsNumber"), visitor(m_lastModified, "lastModified"),
visitor(m_accessRules, "accessRules"), visitor(m_tags, "tags"),
visitor(m_toponyms, "toponyms"), visitor(m_languageCodes, "languageCodes"),
visitor(m_properties, "properties"), VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_annotation, m_description, m_imageUrl, m_authorName, m_authorId,
m_tags, m_toponyms, m_properties)
bool operator==(CategoryData const & data) const
{
double constexpr kEps = 1e-5;
return m_id == data.m_id && m_compilationId == data.m_compilationId && m_type == data.m_type &&
m_name == data.m_name && m_imageUrl == data.m_imageUrl && m_annotation == data.m_annotation &&
m_description == data.m_description && m_visible == data.m_visible && m_accessRules == data.m_accessRules &&
m_authorName == data.m_authorName && m_authorId == data.m_authorId &&
fabs(m_rating - data.m_rating) < kEps && m_reviewsNumber == data.m_reviewsNumber &&
IsEqual(m_lastModified, data.m_lastModified) && m_tags == data.m_tags && m_toponyms == data.m_toponyms &&
m_languageCodes == data.m_languageCodes && m_properties == data.m_properties;
}
bool operator!=(CategoryData const & data) const { return !operator==(data); }
// Unique id (it will not be serialized in text files).
MarkGroupId m_id = kInvalidMarkGroupId;
// Id unique within single kml (have to be serialized in text files).
CompilationId m_compilationId = kInvalidCompilationId;
// Unique ids of nested groups (it will not be serialized in text files).
GroupIdCollection m_compilationIds;
// Compilation's type
CompilationType m_type = CompilationType::Category;
// Category's name.
LocalizableString m_name;
// Image URL.
std::string m_imageUrl;
// Category's description.
LocalizableString m_annotation;
// Category's description.
LocalizableString m_description;
// Collection visibility.
bool m_visible = true;
// Author's name.
std::string m_authorName;
// Author's id.
std::string m_authorId;
// Last modification timestamp.
Timestamp m_lastModified;
// Rating.
double m_rating = 0.0;
// Number of reviews.
uint32_t m_reviewsNumber = 0;
// Access rules.
AccessRules m_accessRules = AccessRules::Local;
// Collection of tags.
std::vector<std::string> m_tags;
// Collection of geo ids for relevant toponyms.
std::vector<std::string> m_toponyms;
// Language codes.
std::vector<int8_t> m_languageCodes;
// Key-value properties.
Properties m_properties;
};
struct FileData
{
DECLARE_VISITOR_AND_DEBUG_PRINT(FileData, visitor(m_serverId, "serverId"), visitor(m_categoryData, "category"),
visitor(m_bookmarksData, "bookmarks"), visitor(m_tracksData, "tracks"),
visitor(m_compilationsData, "compilations"))
bool operator==(FileData const & data) const
{
return m_serverId == data.m_serverId && m_categoryData == data.m_categoryData &&
m_bookmarksData == data.m_bookmarksData && m_tracksData == data.m_tracksData &&
m_compilationsData == data.m_compilationsData;
}
bool operator!=(FileData const & data) const { return !operator==(data); }
// Device id (it will not be serialized in text files).
std::string m_deviceId;
// Server id.
std::string m_serverId;
// Category's data.
CategoryData m_categoryData;
// Bookmarks collection.
std::vector<BookmarkData> m_bookmarksData;
// Tracks collection.
std::vector<TrackData> m_tracksData;
// Compilation collection.
std::vector<CategoryData> m_compilationsData;
};
void SetBookmarksMinZoom(FileData & fileData, double countPerTile, int maxZoom);
inline std::string DebugPrint(BookmarkIcon icon)
{
return ToString(icon);
}
inline std::string DebugPrint(TimestampMillis const & ts)
{
return ::DebugPrint(static_cast<Timestamp>(ts));
}
} // namespace kml

255
libs/kml/types_v3.hpp Normal file
View file

@ -0,0 +1,255 @@
#pragma once
#include "kml/types.hpp"
#include "base/visitor.hpp"
#include <cmath>
#include <ios>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
// V3 kml structures are used to deserialize data of lower versions (V0 - V3).
namespace kml
{
struct BookmarkDataV3
{
DECLARE_VISITOR_AND_DEBUG_PRINT(BookmarkDataV3, visitor(m_id, "id"), visitor(m_name, "name"),
visitor(m_description, "description"), visitor(m_featureTypes, "featureTypes"),
visitor(m_customName, "customName"), visitor(m_color, "color"),
visitor(m_icon, "icon"), visitor(m_viewportScale, "viewportScale"),
visitor(m_timestamp, "timestamp"), visitor(m_point, "point"),
visitor(m_boundTracks, "boundTracks"), VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_description, m_customName)
bool operator==(BookmarkDataV3 const & data) const
{
double constexpr kEps = 1e-5;
return m_id == data.m_id && m_name == data.m_name && m_description == data.m_description &&
m_color == data.m_color && m_icon == data.m_icon && m_viewportScale == data.m_viewportScale &&
IsEqual(m_timestamp, data.m_timestamp) && m_point.EqualDxDy(data.m_point, kEps) &&
m_featureTypes == data.m_featureTypes && m_customName == data.m_customName &&
m_boundTracks == data.m_boundTracks;
}
bool operator!=(BookmarkDataV3 const & data) const { return !operator==(data); }
BookmarkData ConvertToLatestVersion()
{
BookmarkData data;
data.m_id = m_id;
data.m_name = m_name;
data.m_description = m_description;
data.m_featureTypes = m_featureTypes;
data.m_customName = m_customName;
data.m_color = m_color;
data.m_icon = m_icon;
data.m_viewportScale = m_viewportScale;
data.m_timestamp = m_timestamp;
data.m_point = m_point;
data.m_boundTracks = m_boundTracks;
return data;
}
// Unique id (it will not be serialized in text files).
MarkId m_id = kInvalidMarkId;
// Bookmark's name.
LocalizableString m_name;
// Bookmark's description.
LocalizableString m_description;
// Bound feature's types.
std::vector<uint32_t> m_featureTypes;
// Custom bookmark's name.
LocalizableString m_customName;
// Bookmark's color.
ColorData m_color;
// Bookmark's icon.
BookmarkIcon m_icon = BookmarkIcon::None;
// Viewport scale. 0 is a default value (no scale set).
uint8_t m_viewportScale = 0;
// Creation timestamp.
Timestamp m_timestamp = {};
// Coordinates in mercator.
m2::PointD m_point;
// Bound tracks (vector contains local track ids).
std::vector<LocalId> m_boundTracks;
};
struct TrackDataV3
{
DECLARE_VISITOR_AND_DEBUG_PRINT(TrackDataV3, visitor(m_id, "id"), visitor(m_localId, "localId"),
visitor(m_name, "name"), visitor(m_description, "description"),
visitor(m_layers, "layers"), visitor(m_timestamp, "timestamp"),
visitor(m_points, "points"), VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_description)
bool operator==(TrackDataV3 const & data) const
{
return m_id == data.m_id && m_localId == data.m_localId && m_name == data.m_name &&
m_description == data.m_description && m_layers == data.m_layers && IsEqual(m_timestamp, data.m_timestamp) &&
IsEqual(m_points, data.m_points);
}
bool operator!=(TrackDataV3 const & data) const { return !operator==(data); }
TrackData ConvertToLatestVersion()
{
TrackData data;
data.m_id = m_id;
data.m_localId = m_localId;
data.m_name = m_name;
data.m_description = m_description;
data.m_layers = m_layers;
data.m_timestamp = m_timestamp;
data.m_geometry.FromPoints(m_points);
return data;
}
// Unique id (it will not be serialized in text files).
TrackId m_id = kInvalidTrackId;
// Local track id.
LocalId m_localId = 0;
// Track's name.
LocalizableString m_name;
// Track's description.
LocalizableString m_description;
// Layers.
std::vector<TrackLayer> m_layers;
// Creation timestamp.
Timestamp m_timestamp = {};
// Points.
std::vector<m2::PointD> m_points;
};
struct CategoryDataV3
{
DECLARE_VISITOR_AND_DEBUG_PRINT(CategoryDataV3, visitor(m_id, "id"), visitor(m_name, "name"),
visitor(m_imageUrl, "imageUrl"), visitor(m_annotation, "annotation"),
visitor(m_description, "description"), visitor(m_visible, "visible"),
visitor(m_authorName, "authorName"), visitor(m_authorId, "authorId"),
visitor(m_rating, "rating"), visitor(m_reviewsNumber, "reviewsNumber"),
visitor(m_lastModified, "lastModified"), visitor(m_accessRules, "accessRules"),
visitor(m_tags, "tags"), visitor(m_cities, "cities"),
visitor(m_languageCodes, "languageCodes"), visitor(m_properties, "properties"),
VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_annotation, m_description, m_imageUrl, m_authorName, m_authorId,
m_tags, m_properties)
bool operator==(CategoryDataV3 const & data) const
{
double constexpr kEps = 1e-5;
return m_id == data.m_id && m_name == data.m_name && m_imageUrl == data.m_imageUrl &&
m_annotation == data.m_annotation && m_description == data.m_description && m_visible == data.m_visible &&
m_accessRules == data.m_accessRules && m_authorName == data.m_authorName && m_authorId == data.m_authorId &&
fabs(m_rating - data.m_rating) < kEps && m_reviewsNumber == data.m_reviewsNumber &&
IsEqual(m_lastModified, data.m_lastModified) && m_tags == data.m_tags && IsEqual(m_cities, data.m_cities) &&
m_languageCodes == data.m_languageCodes && m_properties == data.m_properties;
}
bool operator!=(CategoryDataV3 const & data) const { return !operator==(data); }
CategoryData ConvertToLatestVersion()
{
// We don't convert m_cities to m_toponyms, since m_cities have been never used.
CategoryData data;
data.m_id = m_id;
data.m_name = m_name;
data.m_imageUrl = m_imageUrl;
data.m_annotation = m_annotation;
data.m_description = m_description;
data.m_visible = m_visible;
data.m_authorName = m_authorName;
data.m_authorId = m_authorId;
data.m_lastModified = m_lastModified;
data.m_rating = m_rating;
data.m_reviewsNumber = m_reviewsNumber;
data.m_accessRules = m_accessRules;
data.m_tags = m_tags;
data.m_languageCodes = m_languageCodes;
data.m_properties = m_properties;
return data;
}
// Unique id (it will not be serialized in text files).
MarkGroupId m_id = kInvalidMarkGroupId;
// Category's name.
LocalizableString m_name;
// Image URL.
std::string m_imageUrl;
// Category's description.
LocalizableString m_annotation;
// Category's description.
LocalizableString m_description;
// Collection visibility.
bool m_visible = true;
// Author's name.
std::string m_authorName;
// Author's id.
std::string m_authorId;
// Last modification timestamp.
Timestamp m_lastModified;
// Rating.
double m_rating = 0.0;
// Number of reviews.
uint32_t m_reviewsNumber = 0;
// Access rules.
AccessRules m_accessRules = AccessRules::Local;
// Collection of tags.
std::vector<std::string> m_tags;
// Collection of cities coordinates.
std::vector<m2::PointD> m_cities;
// Language codes.
std::vector<int8_t> m_languageCodes;
// Key-value properties.
Properties m_properties;
};
struct FileDataV3
{
DECLARE_VISITOR_AND_DEBUG_PRINT(FileDataV3, visitor(m_serverId, "serverId"), visitor(m_categoryData, "category"),
visitor(m_bookmarksData, "bookmarks"), visitor(m_tracksData, "tracks"))
bool operator==(FileDataV3 const & data) const
{
return m_serverId == data.m_serverId && m_categoryData == data.m_categoryData &&
m_bookmarksData == data.m_bookmarksData && m_tracksData == data.m_tracksData;
}
bool operator!=(FileDataV3 const & data) const { return !operator==(data); }
// Device id (unused, it will not be serialized in text files).
std::string m_deviceId;
// Server id.
std::string m_serverId;
// Category's data.
CategoryDataV3 m_categoryData;
// Bookmarks collection.
std::vector<BookmarkDataV3> m_bookmarksData;
// Tracks collection.
std::vector<TrackDataV3> m_tracksData;
FileData ConvertToLatestVersion()
{
FileData data;
data.m_deviceId = m_deviceId;
data.m_serverId = m_serverId;
data.m_categoryData = m_categoryData.ConvertToLatestVersion();
data.m_bookmarksData.reserve(m_bookmarksData.size());
for (auto & d : m_bookmarksData)
data.m_bookmarksData.emplace_back(d.ConvertToLatestVersion());
data.m_tracksData.reserve(m_tracksData.size());
for (auto & d : m_tracksData)
data.m_tracksData.emplace_back(d.ConvertToLatestVersion());
return data;
}
};
} // namespace kml

127
libs/kml/types_v6.hpp Normal file
View file

@ -0,0 +1,127 @@
#pragma once
#include "kml/type_utils.hpp"
#include "kml/types.hpp"
#include "kml/types_v7.hpp"
#include "base/visitor.hpp"
#include <cmath>
#include <ios>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
namespace kml
{
using BookmarkDataV6 = BookmarkDataV7;
// All kml structures for V6 and V7 are same except TrackData.
// Saved V6 version of TrackData to support migration from V6 to V7.
struct TrackDataV6
{
DECLARE_VISITOR_AND_DEBUG_PRINT(TrackDataV6, visitor(m_id, "id"), visitor(m_localId, "localId"),
visitor(m_name, "name"), visitor(m_description, "description"),
visitor(m_layers, "layers"), visitor(m_timestamp, "timestamp"),
visitor(m_points, "points"), visitor(m_visible, "visible"),
visitor(m_nearestToponyms, "nearestToponyms"), visitor(m_properties, "properties"),
VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_description, m_nearestToponyms, m_properties)
bool operator==(TrackDataV6 const & data) const
{
return m_id == data.m_id && m_localId == data.m_localId && m_name == data.m_name &&
m_description == data.m_description && m_layers == data.m_layers && IsEqual(m_timestamp, data.m_timestamp) &&
IsEqual(m_points, data.m_points) && m_visible == data.m_visible &&
m_nearestToponyms == data.m_nearestToponyms && m_properties == data.m_properties;
}
bool operator!=(TrackDataV6 const & data) const { return !operator==(data); }
TrackData ConvertToLatestVersion()
{
TrackData data;
data.m_id = m_id;
data.m_localId = m_localId;
data.m_name = m_name;
data.m_description = m_description;
data.m_layers = m_layers;
data.m_timestamp = m_timestamp;
data.m_geometry.FromPoints(m_points);
data.m_visible = m_visible;
data.m_nearestToponyms = m_nearestToponyms;
data.m_properties = m_properties;
return data;
}
// Unique id (it will not be serialized in text files).
TrackId m_id = kInvalidTrackId;
// Local track id.
LocalId m_localId = 0;
// Track's name.
LocalizableString m_name;
// Track's description.
LocalizableString m_description;
// Layers.
std::vector<TrackLayer> m_layers;
// Creation timestamp.
Timestamp m_timestamp = {};
// Points.
std::vector<m2::PointD> m_points;
// Visibility.
bool m_visible = true;
// Nearest toponyms.
std::vector<std::string> m_nearestToponyms;
// Key-value properties.
Properties m_properties;
};
using CategoryDataV6 = CategoryDataV7;
struct FileDataV6
{
DECLARE_VISITOR_AND_DEBUG_PRINT(FileDataV6, visitor(m_serverId, "serverId"), visitor(m_categoryData, "category"),
visitor(m_bookmarksData, "bookmarks"), visitor(m_tracksData, "tracks"))
bool operator==(FileDataV6 const & data) const
{
return m_serverId == data.m_serverId && m_categoryData == data.m_categoryData &&
m_bookmarksData == data.m_bookmarksData && m_tracksData == data.m_tracksData;
}
bool operator!=(FileDataV6 const & data) const { return !operator==(data); }
FileData ConvertToLatestVersion()
{
FileData data;
data.m_deviceId = m_deviceId;
data.m_serverId = m_serverId;
data.m_categoryData = m_categoryData.ConvertToLatestVersion();
data.m_bookmarksData.reserve(m_bookmarksData.size());
for (auto & d : m_bookmarksData)
data.m_bookmarksData.emplace_back(d.ConvertToLatestVersion());
data.m_tracksData.reserve(m_tracksData.size());
for (auto & track : m_tracksData)
data.m_tracksData.emplace_back(track.ConvertToLatestVersion());
return data;
}
// Device id (it will not be serialized in text files).
std::string m_deviceId;
// Server id.
std::string m_serverId;
// Category's data.
CategoryDataV7 m_categoryData;
// Bookmarks collection.
std::vector<BookmarkDataV6> m_bookmarksData;
// Tracks collection.
std::vector<TrackDataV6> m_tracksData;
};
} // namespace kml

223
libs/kml/types_v7.hpp Normal file
View file

@ -0,0 +1,223 @@
#pragma once
#include "kml/type_utils.hpp"
#include "kml/types.hpp"
#include "kml/types_v8.hpp"
#include "base/visitor.hpp"
#include <cmath>
#include <ios>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
namespace kml
{
// TODO(tomilov): check that BookmarkData changed in V8
struct BookmarkDataV7
{
DECLARE_VISITOR_AND_DEBUG_PRINT(BookmarkDataV7, visitor(m_id, "id"), visitor(m_name, "name"),
visitor(m_description, "description"), visitor(m_featureTypes, "featureTypes"),
visitor(m_customName, "customName"), visitor(m_color, "color"),
visitor(m_icon, "icon"), visitor(m_viewportScale, "viewportScale"),
visitor(m_timestamp, "timestamp"), visitor(m_point, "point"),
visitor(m_boundTracks, "boundTracks"), visitor(m_visible, "visible"),
visitor(m_nearestToponym, "nearestToponym"), visitor(m_properties, "properties"),
VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_description, m_customName, m_nearestToponym, m_properties)
bool operator==(BookmarkDataV7 const & data) const
{
return m_id == data.m_id && m_name == data.m_name && m_description == data.m_description &&
m_color == data.m_color && m_icon == data.m_icon && m_viewportScale == data.m_viewportScale &&
IsEqual(m_timestamp, data.m_timestamp) && m_point.EqualDxDy(data.m_point, kMwmPointAccuracy) &&
m_featureTypes == data.m_featureTypes && m_customName == data.m_customName &&
m_boundTracks == data.m_boundTracks && m_visible == data.m_visible &&
m_nearestToponym == data.m_nearestToponym && m_properties == data.m_properties;
}
bool operator!=(BookmarkDataV7 const & data) const { return !operator==(data); }
BookmarkData ConvertToLatestVersion()
{
BookmarkData data;
data.m_id = m_id;
data.m_name = m_name;
data.m_description = m_description;
data.m_featureTypes = m_featureTypes;
data.m_customName = m_customName;
data.m_color = m_color;
data.m_icon = m_icon;
data.m_viewportScale = m_viewportScale;
data.m_timestamp = m_timestamp;
data.m_point = m_point;
data.m_boundTracks = m_boundTracks;
data.m_visible = m_visible;
data.m_nearestToponym = m_nearestToponym;
data.m_properties = m_properties;
return data;
}
// Unique id (it will not be serialized in text files).
MarkId m_id = kInvalidMarkId;
// Bookmark's name.
LocalizableString m_name;
// Bookmark's description.
LocalizableString m_description;
// Bound feature's types: type indices sorted by importance, the most
// important one goes first.
std::vector<uint32_t> m_featureTypes;
// Custom bookmark's name.
LocalizableString m_customName;
// Bookmark's color.
ColorData m_color;
// Bookmark's icon.
BookmarkIcon m_icon = BookmarkIcon::None;
// Viewport scale. 0 is a default value (no scale set).
uint8_t m_viewportScale = 0;
// Creation timestamp.
Timestamp m_timestamp = {};
// Coordinates in mercator.
m2::PointD m_point;
// Bound tracks (vector contains local track ids).
std::vector<LocalId> m_boundTracks;
// Visibility.
bool m_visible = true;
// Nearest toponym.
std::string m_nearestToponym;
// Key-value properties.
Properties m_properties;
};
using TrackDataV7 = TrackDataV8;
// TODO(tomilov): check that CategoryData changed in V8
struct CategoryDataV7
{
DECLARE_VISITOR_AND_DEBUG_PRINT(CategoryDataV7, visitor(m_id, "id"), visitor(m_name, "name"),
visitor(m_imageUrl, "imageUrl"), visitor(m_annotation, "annotation"),
visitor(m_description, "description"), visitor(m_visible, "visible"),
visitor(m_authorName, "authorName"), visitor(m_authorId, "authorId"),
visitor(m_rating, "rating"), visitor(m_reviewsNumber, "reviewsNumber"),
visitor(m_lastModified, "lastModified"), visitor(m_accessRules, "accessRules"),
visitor(m_tags, "tags"), visitor(m_toponyms, "toponyms"),
visitor(m_languageCodes, "languageCodes"), visitor(m_properties, "properties"),
VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_annotation, m_description, m_imageUrl, m_authorName, m_authorId,
m_tags, m_toponyms, m_properties)
bool operator==(CategoryDataV7 const & data) const
{
double constexpr kEps = 1e-5;
return m_id == data.m_id && m_name == data.m_name && m_imageUrl == data.m_imageUrl &&
m_annotation == data.m_annotation && m_description == data.m_description && m_visible == data.m_visible &&
m_accessRules == data.m_accessRules && m_authorName == data.m_authorName && m_authorId == data.m_authorId &&
fabs(m_rating - data.m_rating) < kEps && m_reviewsNumber == data.m_reviewsNumber &&
IsEqual(m_lastModified, data.m_lastModified) && m_tags == data.m_tags && m_toponyms == data.m_toponyms &&
m_languageCodes == data.m_languageCodes && m_properties == data.m_properties;
}
bool operator!=(CategoryDataV7 const & data) const { return !operator==(data); }
CategoryData ConvertToLatestVersion()
{
CategoryData data;
data.m_id = m_id;
data.m_name = m_name;
data.m_imageUrl = m_imageUrl;
data.m_annotation = m_annotation;
data.m_description = m_description;
data.m_visible = m_visible;
data.m_authorName = m_authorName;
data.m_authorId = m_authorId;
data.m_lastModified = m_lastModified;
data.m_rating = m_rating;
data.m_reviewsNumber = m_reviewsNumber;
data.m_accessRules = m_accessRules;
data.m_tags = m_tags;
data.m_toponyms = m_toponyms;
data.m_languageCodes = m_languageCodes;
data.m_properties = m_properties;
return data;
}
// Unique id (it will not be serialized in text files).
MarkGroupId m_id = kInvalidMarkGroupId;
// Category's name.
LocalizableString m_name;
// Image URL.
std::string m_imageUrl;
// Category's description.
LocalizableString m_annotation;
// Category's description.
LocalizableString m_description;
// Collection visibility.
bool m_visible = true;
// Author's name.
std::string m_authorName;
// Author's id.
std::string m_authorId;
// Last modification timestamp.
Timestamp m_lastModified;
// Rating.
double m_rating = 0.0;
// Number of reviews.
uint32_t m_reviewsNumber = 0;
// Access rules.
AccessRules m_accessRules = AccessRules::Local;
// Collection of tags.
std::vector<std::string> m_tags;
// Collection of geo ids for relevant toponyms.
std::vector<std::string> m_toponyms;
// Language codes.
std::vector<int8_t> m_languageCodes;
// Key-value properties.
Properties m_properties;
};
struct FileDataV7
{
DECLARE_VISITOR_AND_DEBUG_PRINT(FileDataV7, visitor(m_serverId, "serverId"), visitor(m_categoryData, "category"),
visitor(m_bookmarksData, "bookmarks"), visitor(m_tracksData, "tracks"))
bool operator==(FileDataV7 const & data) const
{
return m_serverId == data.m_serverId && m_categoryData == data.m_categoryData &&
m_bookmarksData == data.m_bookmarksData && m_tracksData == data.m_tracksData;
}
bool operator!=(FileDataV7 const & data) const { return !operator==(data); }
FileData ConvertToLatestVersion()
{
FileData data;
data.m_deviceId = m_deviceId;
data.m_serverId = m_serverId;
data.m_categoryData = m_categoryData.ConvertToLatestVersion();
data.m_bookmarksData.reserve(m_bookmarksData.size());
for (auto & d : m_bookmarksData)
data.m_bookmarksData.emplace_back(d.ConvertToLatestVersion());
data.m_tracksData = m_tracksData;
return data;
}
// Device id (it will not be serialized in text files).
std::string m_deviceId;
// Server id.
std::string m_serverId;
// Category's data.
CategoryDataV7 m_categoryData;
// Bookmarks collection.
std::vector<BookmarkDataV7> m_bookmarksData;
// Tracks collection.
std::vector<TrackDataV7> m_tracksData;
};
} // namespace kml

160
libs/kml/types_v8.hpp Normal file
View file

@ -0,0 +1,160 @@
#pragma once
#include "kml/types_v9.hpp"
namespace kml
{
struct BookmarkDataV8
{
DECLARE_VISITOR_AND_DEBUG_PRINT(BookmarkDataV8, visitor(m_id, "id"), visitor(m_name, "name"),
visitor(m_description, "description"), visitor(m_featureTypes, "featureTypes"),
visitor(m_customName, "customName"), visitor(m_color, "color"),
visitor(m_icon, "icon"), visitor(m_viewportScale, "viewportScale"),
visitor(m_timestamp, "timestamp"), visitor(m_point, "point"),
visitor(m_boundTracks, "boundTracks"), visitor(m_visible, "visible"),
visitor(m_nearestToponym, "nearestToponym"), visitor(m_properties, "properties"),
visitor(m_compilations, "compilations"), VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_description, m_customName, m_nearestToponym, m_properties)
bool operator==(BookmarkDataV8 const & data) const
{
return m_id == data.m_id && m_name == data.m_name && m_description == data.m_description &&
m_color == data.m_color && m_icon == data.m_icon && m_viewportScale == data.m_viewportScale &&
IsEqual(m_timestamp, data.m_timestamp) && m_point.EqualDxDy(data.m_point, kMwmPointAccuracy) &&
m_featureTypes == data.m_featureTypes && m_customName == data.m_customName &&
m_boundTracks == data.m_boundTracks && m_visible == data.m_visible &&
m_nearestToponym == data.m_nearestToponym && m_properties == data.m_properties &&
m_compilations == data.m_compilations;
}
bool operator!=(BookmarkDataV8 const & data) const { return !operator==(data); }
BookmarkDataV8() = default;
// Initialize using latest version of BookmarkData
BookmarkDataV8(BookmarkData const & src)
{
// Copy all fields from `src` except `m_minZoom`
m_id = src.m_id;
m_name = src.m_name;
m_description = src.m_description;
m_featureTypes = src.m_featureTypes;
m_customName = src.m_customName;
m_color = src.m_color;
m_icon = src.m_icon;
m_viewportScale = src.m_viewportScale;
m_timestamp = src.m_timestamp;
m_point = src.m_point;
m_boundTracks = src.m_boundTracks;
m_visible = src.m_visible;
m_nearestToponym = src.m_nearestToponym;
m_properties = src.m_properties;
m_compilations = src.m_compilations;
m_collectionIndex = src.m_collectionIndex;
}
BookmarkData ConvertToLatestVersion()
{
BookmarkData data;
data.m_id = m_id;
data.m_name = m_name;
data.m_description = m_description;
data.m_featureTypes = m_featureTypes;
data.m_customName = m_customName;
data.m_color = m_color;
data.m_icon = m_icon;
data.m_viewportScale = m_viewportScale;
data.m_timestamp = m_timestamp;
data.m_point = m_point;
data.m_boundTracks = m_boundTracks;
data.m_visible = m_visible;
data.m_nearestToponym = m_nearestToponym;
data.m_properties = m_properties;
data.m_compilations = m_compilations;
return data;
}
// Unique id (it will not be serialized in text files).
MarkId m_id = kInvalidMarkId;
// Bookmark's name.
LocalizableString m_name;
// Bookmark's description.
LocalizableString m_description;
// Bound feature's types: type indices sorted by importance, the most
// important one goes first.
std::vector<uint32_t> m_featureTypes;
// Custom bookmark's name.
LocalizableString m_customName;
// Bookmark's color.
ColorData m_color;
// Bookmark's icon.
BookmarkIcon m_icon = BookmarkIcon::None;
// Viewport scale. 0 is a default value (no scale set).
uint8_t m_viewportScale = 0;
// Creation timestamp.
Timestamp m_timestamp = {};
// Coordinates in mercator.
m2::PointD m_point;
// Bound tracks (vector contains local track ids).
std::vector<LocalId> m_boundTracks;
// Visibility.
bool m_visible = true;
// Nearest toponym.
std::string m_nearestToponym;
// Key-value properties.
Properties m_properties;
// List of compilationIds.
std::vector<CompilationId> m_compilations;
};
using TrackDataV8 = TrackDataV9;
using CategoryDataV8 = CategoryDataV9;
struct FileDataV8
{
DECLARE_VISITOR_AND_DEBUG_PRINT(FileDataV8, visitor(m_serverId, "serverId"), visitor(m_categoryData, "category"),
visitor(m_bookmarksData, "bookmarks"), visitor(m_tracksData, "tracks"),
visitor(m_compilationData, "compilations"))
bool operator==(FileDataV8 const & data) const
{
return m_serverId == data.m_serverId && m_categoryData == data.m_categoryData &&
m_bookmarksData == data.m_bookmarksData && m_tracksData == data.m_tracksData &&
m_compilationData == data.m_compilationData;
}
bool operator!=(FileDataV8 const & data) const { return !operator==(data); }
FileData ConvertToLatestVersion()
{
FileData data;
data.m_deviceId = m_deviceId;
data.m_serverId = m_serverId;
data.m_categoryData = m_categoryData;
data.m_bookmarksData.reserve(m_bookmarksData.size());
for (auto & d : m_bookmarksData)
data.m_bookmarksData.emplace_back(d.ConvertToLatestVersion());
data.m_tracksData = m_tracksData;
return data;
}
// Device id (it will not be serialized in text files).
std::string m_deviceId;
// Server id.
std::string m_serverId;
// Category's data.
CategoryDataV8 m_categoryData;
// Bookmarks collection.
std::vector<BookmarkDataV8> m_bookmarksData;
// Tracks collection.
std::vector<TrackDataV8> m_tracksData;
// Compilation collection.
std::vector<CategoryDataV8> m_compilationData;
};
} // namespace kml

283
libs/kml/types_v8mm.hpp Normal file
View file

@ -0,0 +1,283 @@
#pragma once
#include "kml/types.hpp"
namespace kml
{
// BookmarkDataV8MM contains the same fields as BookmarkDataV8 but without compilations.
struct BookmarkDataV8MM
{
DECLARE_VISITOR_AND_DEBUG_PRINT(BookmarkDataV8MM, visitor(m_id, "id"), visitor(m_name, "name"),
visitor(m_description, "description"), visitor(m_featureTypes, "featureTypes"),
visitor(m_customName, "customName"), visitor(m_color, "color"),
visitor(m_icon, "icon"), visitor(m_viewportScale, "viewportScale"),
visitor(m_timestamp, "timestamp"), visitor(m_point, "point"),
visitor(m_boundTracks, "boundTracks"), visitor(m_visible, "visible"),
visitor(m_nearestToponym, "nearestToponym"), visitor(m_properties, "properties"),
VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_description, m_customName, m_nearestToponym, m_properties)
bool operator==(BookmarkDataV8MM const & data) const
{
return m_id == data.m_id && m_name == data.m_name && m_description == data.m_description &&
m_color == data.m_color && m_icon == data.m_icon && m_viewportScale == data.m_viewportScale &&
IsEqual(m_timestamp, data.m_timestamp) && m_point.EqualDxDy(data.m_point, kMwmPointAccuracy) &&
m_featureTypes == data.m_featureTypes && m_customName == data.m_customName &&
m_boundTracks == data.m_boundTracks && m_visible == data.m_visible &&
m_nearestToponym == data.m_nearestToponym && m_properties == data.m_properties;
}
bool operator!=(BookmarkDataV8MM const & data) const { return !operator==(data); }
BookmarkData ConvertToLatestVersion() const
{
BookmarkData data;
data.m_id = m_id;
data.m_name = m_name;
data.m_description = m_description;
data.m_featureTypes = m_featureTypes;
data.m_customName = m_customName;
data.m_color = m_color;
data.m_icon = m_icon;
data.m_viewportScale = m_viewportScale;
data.m_timestamp = m_timestamp;
data.m_point = m_point;
data.m_boundTracks = m_boundTracks;
data.m_visible = m_visible;
data.m_nearestToponym = m_nearestToponym;
data.m_properties = m_properties;
return data;
}
// Unique id (it will not be serialized in text files).
MarkId m_id = kInvalidMarkId;
// Bookmark's name.
LocalizableString m_name;
// Bookmark's description.
LocalizableString m_description;
// Bound feature's types: type indices sorted by importance, the most
// important one goes first.
std::vector<uint32_t> m_featureTypes;
// Custom bookmark's name.
LocalizableString m_customName;
// Bookmark's color.
ColorData m_color;
// Bookmark's icon.
BookmarkIcon m_icon = BookmarkIcon::None;
// Viewport scale. 0 is a default value (no scale set).
uint8_t m_viewportScale = 0;
// Creation timestamp.
TimestampMillis m_timestamp{};
// Coordinates in mercator.
m2::PointD m_point;
// Bound tracks (vector contains local track ids).
std::vector<LocalId> m_boundTracks;
// Visibility.
bool m_visible = true;
// Nearest toponym.
std::string m_nearestToponym;
// Key-value properties.
Properties m_properties;
};
struct TrackDataV8MM
{
DECLARE_VISITOR_AND_DEBUG_PRINT(TrackDataV8MM, visitor(m_id, "id"), visitor(m_localId, "localId"),
visitor(m_name, "name"), visitor(m_description, "description"),
visitor(m_layers, "layers"), visitor(m_timestamp, "timestamp"),
visitor(m_geometry, "geometry"), visitor(m_visible, "visible"),
visitor(m_constant1, "constant1"), // Extra field introduced in V8MM.
visitor(m_constant2, "constant2"), // Extra field introduced in V8MM.
visitor(m_constant3, "constant3"), // Extra field introduced in V8MM.
visitor(m_nearestToponyms, "nearestToponyms"), visitor(m_properties, "properties"),
VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_description, m_nearestToponyms, m_properties)
bool operator==(TrackDataV8MM const & data) const
{
return m_id == data.m_id && m_localId == data.m_localId && m_name == data.m_name &&
m_description == data.m_description && m_layers == data.m_layers && IsEqual(m_timestamp, data.m_timestamp) &&
m_geometry == data.m_geometry && m_visible == data.m_visible &&
m_nearestToponyms == data.m_nearestToponyms && m_properties == data.m_properties;
}
bool operator!=(TrackDataV8MM const & data) const { return !operator==(data); }
TrackData ConvertToLatestVersion() const
{
TrackData data;
data.m_id = m_id;
data.m_localId = m_localId;
data.m_name = m_name;
data.m_description = m_description;
data.m_layers = m_layers;
data.m_timestamp = m_timestamp;
data.m_geometry = m_geometry;
data.m_visible = m_visible;
data.m_nearestToponyms = m_nearestToponyms;
data.m_properties = m_properties;
return data;
}
// Unique id (it will not be serialized in text files).
TrackId m_id = kInvalidTrackId;
// Local track id.
LocalId m_localId = 0;
// Track's name.
LocalizableString m_name;
// Track's description.
LocalizableString m_description;
// Layers.
std::vector<TrackLayer> m_layers;
// Creation timestamp.
TimestampMillis m_timestamp{};
MultiGeometry m_geometry;
// Visibility.
bool m_visible = true;
// These constants were introduced in KMB V8MM. Usually have value 0. Don't know its purpose.
uint8_t m_constant1 = 0;
uint8_t m_constant2 = 0;
uint8_t m_constant3 = 0;
// Nearest toponyms.
std::vector<std::string> m_nearestToponyms;
// Key-value properties.
Properties m_properties;
};
// CategoryData8MM contains the same fields as CategoryData8 but with no compilations
struct CategoryDataV8MM
{
DECLARE_VISITOR_AND_DEBUG_PRINT(CategoryDataV8MM, visitor(m_id, "id"), visitor(m_name, "name"),
visitor(m_imageUrl, "imageUrl"), visitor(m_annotation, "annotation"),
visitor(m_description, "description"), visitor(m_visible, "visible"),
visitor(m_authorName, "authorName"), visitor(m_authorId, "authorId"),
visitor(m_rating, "rating"), visitor(m_reviewsNumber, "reviewsNumber"),
visitor(m_lastModified, "lastModified"), visitor(m_accessRules, "accessRules"),
visitor(m_tags, "tags"), visitor(m_toponyms, "toponyms"),
visitor(m_languageCodes, "languageCodes"), visitor(m_properties, "properties"),
VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_annotation, m_description, m_imageUrl, m_authorName, m_authorId,
m_tags, m_toponyms, m_properties)
bool operator==(CategoryDataV8MM const & data) const
{
double constexpr kEps = 1e-5;
return m_id == data.m_id && m_name == data.m_name && m_imageUrl == data.m_imageUrl &&
m_annotation == data.m_annotation && m_description == data.m_description && m_visible == data.m_visible &&
m_accessRules == data.m_accessRules && m_authorName == data.m_authorName && m_authorId == data.m_authorId &&
fabs(m_rating - data.m_rating) < kEps && m_reviewsNumber == data.m_reviewsNumber &&
IsEqual(m_lastModified, data.m_lastModified) && m_tags == data.m_tags && m_toponyms == data.m_toponyms &&
m_languageCodes == data.m_languageCodes && m_properties == data.m_properties;
}
CategoryData ConvertToLatestVersion() const
{
CategoryData data;
data.m_id = m_id;
data.m_type = CompilationType::Category; // Format V8MM doesn't have m_type. Using default
data.m_name = m_name;
data.m_imageUrl = m_imageUrl;
data.m_annotation = m_annotation;
data.m_description = m_description;
data.m_visible = m_visible;
data.m_authorName = m_authorName;
data.m_authorId = m_authorId;
data.m_rating = m_rating;
data.m_reviewsNumber = m_reviewsNumber;
data.m_lastModified = m_lastModified;
data.m_accessRules = m_accessRules;
data.m_tags = m_tags;
data.m_toponyms = m_toponyms;
data.m_languageCodes = m_languageCodes;
data.m_properties = m_properties;
return data;
}
bool operator!=(CategoryDataV8MM const & data) const { return !operator==(data); }
// Unique id (it will not be serialized in text files).
MarkGroupId m_id = kInvalidMarkGroupId;
// Category's name.
LocalizableString m_name;
// Image URL.
std::string m_imageUrl;
// Category's description.
LocalizableString m_annotation;
// Category's description.
LocalizableString m_description;
// Collection visibility.
bool m_visible = true;
// Author's name.
std::string m_authorName;
// Author's id.
std::string m_authorId;
// Last modification timestamp.
TimestampMillis m_lastModified{};
// Rating.
double m_rating = 0.0;
// Number of reviews.
uint32_t m_reviewsNumber = 0;
// Access rules.
AccessRules m_accessRules = AccessRules::Local;
// Collection of tags.
std::vector<std::string> m_tags;
// Collection of geo ids for relevant toponyms.
std::vector<std::string> m_toponyms;
// Language codes.
std::vector<int8_t> m_languageCodes;
// Key-value properties.
Properties m_properties;
};
// FileDataV8MM contains the same sections as FileDataV8 but with no compilations
template <class TrackDataT>
struct FileDataMMImpl
{
DECLARE_VISITOR_AND_DEBUG_PRINT(FileDataMMImpl, visitor(m_serverId, "serverId"), visitor(m_categoryData, "category"),
visitor(m_bookmarksData, "bookmarks"), visitor(m_tracksData, "tracks"))
bool operator==(FileDataMMImpl const & data) const
{
return m_serverId == data.m_serverId && m_categoryData == data.m_categoryData &&
m_bookmarksData == data.m_bookmarksData && m_tracksData == data.m_tracksData;
}
bool operator!=(FileDataMMImpl const & data) const { return !operator==(data); }
FileData ConvertToLatestVersion()
{
FileData data;
data.m_deviceId = m_deviceId;
data.m_serverId = m_serverId;
data.m_categoryData = m_categoryData.ConvertToLatestVersion();
data.m_bookmarksData.reserve(m_bookmarksData.size());
for (auto & d : m_bookmarksData)
data.m_bookmarksData.emplace_back(d.ConvertToLatestVersion());
data.m_tracksData.reserve(m_tracksData.size());
for (auto & t : m_tracksData)
data.m_tracksData.emplace_back(t.ConvertToLatestVersion());
return data;
}
// Device id (it will not be serialized in text files).
std::string m_deviceId;
// Server id.
std::string m_serverId;
// Category's data.
CategoryDataV8MM m_categoryData;
// Bookmarks collection.
std::vector<BookmarkDataV8MM> m_bookmarksData;
// Tracks collection.
std::vector<TrackDataT> m_tracksData;
};
using FileDataV8MM = FileDataMMImpl<TrackDataV8MM>;
} // namespace kml

11
libs/kml/types_v9.hpp Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include "kml/types.hpp"
namespace kml
{
using BookmarkDataV9 = BookmarkData;
using TrackDataV9 = TrackData;
using CategoryDataV9 = CategoryData;
using FileDataV9 = FileData;
} // namespace kml

45
libs/kml/types_v9mm.hpp Normal file
View file

@ -0,0 +1,45 @@
#pragma once
#include "kml/types.hpp"
#include "kml/types_v8mm.hpp"
namespace kml
{
MultiGeometry mergeGeometry(std::vector<MultiGeometry> && aGeometries);
struct TrackDataV9MM : TrackDataV8MM
{
DECLARE_VISITOR_AND_DEBUG_PRINT(
TrackDataV9MM, visitor(m_id, "id"), visitor(m_localId, "localId"), visitor(m_name, "name"),
visitor(m_description, "description"), visitor(m_layers, "layers"), visitor(m_timestamp, "timestamp"),
visitor(m_multiGeometry, "multiGeometry"), // V9MM introduced multiGeometry instead of a single one
visitor(m_visible, "visible"), visitor(m_constant1, "constant1"), visitor(m_constant2, "constant2"),
visitor(m_constant3, "constant3"), visitor(m_nearestToponyms, "nearestToponyms"),
visitor(m_properties, "properties"), VISITOR_COLLECTABLE)
DECLARE_COLLECTABLE(LocalizableStringIndex, m_name, m_description, m_nearestToponyms, m_properties)
TrackData ConvertToLatestVersion()
{
TrackData data;
data.m_id = m_id;
data.m_localId = m_localId;
data.m_name = m_name;
data.m_description = m_description;
data.m_layers = m_layers;
data.m_timestamp = m_timestamp;
data.m_geometry = mergeGeometry(std::move(m_multiGeometry));
data.m_visible = m_visible;
data.m_nearestToponyms = m_nearestToponyms;
data.m_properties = m_properties;
return data;
}
std::vector<MultiGeometry> m_multiGeometry;
};
// Contains the same sections as FileDataV8MM but with changed m_tracksData format
using FileDataV9MM = FileDataMMImpl<TrackDataV9MM>;
} // namespace kml

744
libs/kml/visitors.hpp Normal file
View file

@ -0,0 +1,744 @@
#pragma once
#include "kml/types.hpp"
#include "kml/types_v3.hpp"
#include "kml/types_v6.hpp"
#include "kml/types_v7.hpp"
#include "coding/geometry_coding.hpp"
#include "coding/point_coding.hpp"
#include "coding/text_storage.hpp"
#include "coding/varint.hpp"
#include "geometry/point_with_altitude.hpp"
#include <algorithm>
#include <type_traits>
#include <vector>
namespace kml
{
template <typename Collector>
class CollectorVisitor
{
// The class checks for the existence of collection methods.
template <typename T>
class HasCollectionMethods
{
template <typename C>
static char Test(decltype(&C::ClearCollectionIndex));
template <typename C>
static int Test(...);
public:
enum
{
value = sizeof(Test<T>(0)) == sizeof(char)
};
};
// All types which will be visited to collect.
template <typename T>
class VisitedTypes
{
public:
enum
{
value = std::is_same<T, BookmarkData>::value || std::is_same<T, TrackData>::value ||
std::is_same<T, CategoryData>::value || std::is_same<T, FileData>::value ||
std::is_same<T, BookmarkDataV3>::value || std::is_same<T, TrackDataV3>::value ||
std::is_same<T, CategoryDataV3>::value || std::is_same<T, FileDataV3>::value ||
std::is_same<T, BookmarkDataV6>::value || std::is_same<T, TrackDataV6>::value ||
std::is_same<T, CategoryDataV6>::value || std::is_same<T, FileDataV6>::value ||
std::is_same<T, BookmarkDataV7>::value || std::is_same<T, TrackDataV7>::value ||
std::is_same<T, CategoryDataV7>::value || std::is_same<T, FileDataV7>::value
};
};
public:
explicit CollectorVisitor(Collector & collector, bool clearIndex = false)
: m_collector(collector)
, m_clearIndex(clearIndex)
{}
template <typename T>
std::enable_if_t<HasCollectionMethods<T>::value> PerformActionIfPossible(T & t)
{
if (m_clearIndex)
t.ClearCollectionIndex();
else
t.Collect(m_collector);
}
template <typename T>
std::enable_if_t<!HasCollectionMethods<T>::value> PerformActionIfPossible(T & t)
{}
template <typename T>
std::enable_if_t<VisitedTypes<T>::value> VisitIfPossible(T & t)
{
t.Visit(*this);
}
template <typename T>
std::enable_if_t<!VisitedTypes<T>::value> VisitIfPossible(T & t)
{}
template <typename T>
void operator()(T & t, char const * /* name */ = nullptr)
{
PerformActionIfPossible(t);
VisitIfPossible(t);
}
template <typename T>
void operator()(std::vector<T> & vs, char const * /* name */ = nullptr)
{
for (auto & v : vs)
(*this)(v);
}
private:
Collector & m_collector;
bool const m_clearIndex;
};
class LocalizableStringCollector
{
public:
explicit LocalizableStringCollector(size_t reservedCollectionSize)
{
m_collection.reserve(reservedCollectionSize + 1);
m_collection.emplace_back(std::string());
}
template <typename... OtherStrings>
void Collect(LocalizableStringIndex & index, LocalizableString const & str, OtherStrings const &... args)
{
index.emplace_back(LocalizableStringSubIndex());
for (auto const & p : str)
CollectString(index.back(), p.first, p.second);
Collect(index, args...);
}
template <typename... OtherStrings>
void Collect(LocalizableStringIndex & index, std::string const & str, OtherStrings const &... args)
{
int8_t constexpr kFakeIndex = 0;
index.emplace_back(LocalizableStringSubIndex());
CollectString(index.back(), kFakeIndex, str);
Collect(index, args...);
}
template <typename... OtherStrings>
void Collect(LocalizableStringIndex & index, std::vector<std::string> const & stringsArray,
OtherStrings const &... args)
{
index.emplace_back(LocalizableStringSubIndex());
auto constexpr kMaxSize = static_cast<size_t>(std::numeric_limits<int8_t>::max());
auto const sz = std::min(stringsArray.size(), kMaxSize);
for (size_t i = 0; i < sz; ++i)
CollectString(index.back(), static_cast<int8_t>(i), stringsArray[i]);
Collect(index, args...);
}
template <typename... OtherStrings>
void Collect(LocalizableStringIndex & index, Properties const & properties, OtherStrings const &... args)
{
index.emplace_back(LocalizableStringSubIndex());
auto constexpr kMaxSize = std::numeric_limits<int8_t>::max() - 1;
int8_t counter = 0;
for (auto const & p : properties)
{
if (counter >= kMaxSize)
break;
CollectString(index.back(), counter++, p.first);
CollectString(index.back(), counter++, p.second);
}
Collect(index, args...);
}
template <typename...>
void Collect(LocalizableStringIndex & index)
{}
std::vector<std::string> && StealCollection() { return std::move(m_collection); }
private:
void CollectString(LocalizableStringSubIndex & subIndex, int8_t code, std::string const & str)
{
if (str.empty())
{
subIndex.insert(std::make_pair(code, kEmptyStringId));
}
else
{
subIndex.insert(std::make_pair(code, m_counter));
m_counter++;
m_collection.push_back(str);
}
}
uint32_t m_counter = kEmptyStringId + 1;
std::vector<std::string> m_collection;
};
namespace binary
{
template <typename Sink>
void WriteLocalizableStringIndex(Sink & sink, LocalizableStringIndex const & index)
{
WriteVarUint(sink, static_cast<uint32_t>(index.size()));
for (auto const & subIndex : index)
{
WriteVarUint(sink, static_cast<uint32_t>(subIndex.size()));
for (auto const & p : subIndex)
{
WriteToSink(sink, p.first);
WriteVarUint(sink, p.second);
}
}
}
template <typename Source>
void ReadLocalizableStringIndex(Source & source, LocalizableStringIndex & index)
{
auto const indexSize = ReadVarUint<uint32_t, Source>(source);
index.reserve(indexSize);
for (uint32_t i = 0; i < indexSize; ++i)
{
index.emplace_back(LocalizableStringSubIndex());
auto & subIndex = index.back();
auto const subIndexSize = ReadVarUint<uint32_t, Source>(source);
for (uint32_t j = 0; j < subIndexSize; ++j)
{
auto const lang = ReadPrimitiveFromSource<int8_t>(source);
auto const strIndex = ReadVarUint<uint32_t, Source>(source);
subIndex[lang] = strIndex;
}
}
}
template <typename Sink>
inline void WritePointU(Sink & sink, m2::PointU const & pt)
{
WriteVarUint(sink, pt.x);
WriteVarUint(sink, pt.y);
}
template <typename Sink>
inline void WritePointD(Sink & sink, m2::PointD const & pt, uint8_t doubleBits)
{
WritePointU(sink, PointDToPointU(pt, doubleBits));
}
template <typename Source>
inline m2::PointU ReadPointU(Source & source)
{
auto x = ReadVarUint<uint32_t>(source);
auto y = ReadVarUint<uint32_t>(source);
return {x, y};
}
template <typename Source>
inline m2::PointD ReadPointD(Source & source, uint8_t doubleBits)
{
return PointUToPointD(ReadPointU(source), doubleBits);
}
template <typename Sink>
class CategorySerializerVisitor
{
public:
explicit CategorySerializerVisitor(Sink & sink, uint8_t doubleBits) : m_sink(sink), m_doubleBits(doubleBits) {}
void operator()(LocalizableStringIndex const & index, char const * /* name */ = nullptr)
{
WriteLocalizableStringIndex(m_sink, index);
}
void operator()(bool b, char const * /* name */ = nullptr) { (*this)(static_cast<uint8_t>(b)); }
void operator()(AccessRules rules, char const * /* name */ = nullptr) { (*this)(static_cast<uint8_t>(rules)); }
void operator()(CompilationType type, char const * /* name */ = nullptr) { (*this)(static_cast<uint8_t>(type)); }
void operator()(Timestamp const & t, char const * /* name */ = nullptr)
{
WriteVarUint(m_sink, ToSecondsSinceEpoch(t));
}
void operator()(TimestampMillis const & t, char const * /* name */ = nullptr)
{
WriteVarUint(m_sink, ToSecondsSinceEpoch(t) * 1000);
}
void operator()(double d, char const * /* name */ = nullptr)
{
auto const encoded = DoubleToUint32(d, kMinRating, kMaxRating, m_doubleBits);
WriteVarUint(m_sink, encoded);
}
void operator()(m2::PointD const & pt, char const * /* name */ = nullptr) { WritePointD(m_sink, pt, m_doubleBits); }
void operator()(CategoryData const & compilationData, char const * /* name */ = nullptr)
{
compilationData.Visit(*this);
}
template <typename T>
void operator()(std::vector<T> const & vs, char const * /* name */ = nullptr)
{
WriteVarUint(m_sink, static_cast<uint32_t>(vs.size()));
for (auto const & v : vs)
(*this)(v);
}
template <typename D>
std::enable_if_t<std::is_integral<D>::value> operator()(D d, char const * /* name */ = nullptr)
{
WriteToSink(m_sink, d);
}
template <typename R>
std::enable_if_t<!std::is_integral<R>::value> operator()(R const & r, char const * /* name */ = nullptr)
{
r.Visit(*this);
}
// Skip visiting. It is stored in the separate sections.
SKIP_VISITING(LocalizableString const &)
SKIP_VISITING(std::string const &)
SKIP_VISITING(std::vector<std::string> const &)
SKIP_VISITING(Properties const &)
SKIP_VISITING(std::vector<BookmarkData> const &)
SKIP_VISITING(std::vector<TrackData> const &)
private:
Sink & m_sink;
uint8_t const m_doubleBits;
};
template <typename Sink>
class BookmarkSerializerVisitor
{
public:
explicit BookmarkSerializerVisitor(Sink & sink, uint8_t doubleBits) : m_sink(sink), m_doubleBits(doubleBits) {}
void operator()(LocalizableStringIndex const & index, char const * /* name */ = nullptr)
{
WriteLocalizableStringIndex(m_sink, index);
}
void operator()(bool b, char const * /* name */ = nullptr) { (*this)(static_cast<uint8_t>(b)); }
void operator()(m2::PointD const & pt, char const * /* name */ = nullptr) { WritePointD(m_sink, pt, m_doubleBits); }
void operator()(geometry::PointWithAltitude const & pt, char const * /* name */ = nullptr)
{
WritePointD(m_sink, pt.GetPoint(), m_doubleBits);
WriteVarInt(m_sink, pt.GetAltitude());
}
void operator()(double d, char const * /* name */ = nullptr)
{
auto const encoded = DoubleToUint32(d, kMinLineWidth, kMaxLineWidth, m_doubleBits);
WriteVarUint(m_sink, encoded);
}
void operator()(Timestamp const & t, char const * /* name */ = nullptr)
{
WriteVarUint(m_sink, ToSecondsSinceEpoch(t));
}
void operator()(TimestampMillis const & t, char const * /* name */ = nullptr)
{
WriteVarUint(m_sink, ToSecondsSinceEpoch(t) * 1000);
}
void operator()(PredefinedColor color, char const * /* name */ = nullptr) { (*this)(static_cast<uint8_t>(color)); }
void operator()(BookmarkIcon icon, char const * /* name */ = nullptr) { (*this)(static_cast<uint16_t>(icon)); }
template <typename T>
void operator()(std::vector<T> const & vs, char const * /* name */ = nullptr)
{
WriteVarUint(m_sink, static_cast<uint32_t>(vs.size()));
for (auto const & v : vs)
(*this)(v);
}
template <class T>
void SavePointsSequence(std::vector<T> const & points)
{
WriteVarUint(m_sink, static_cast<uint32_t>(points.size()));
m2::PointU lastUpt = m2::PointU::Zero();
for (uint32_t i = 0; i < static_cast<uint32_t>(points.size()); ++i)
{
auto const upt = PointDToPointU(points[i], m_doubleBits);
coding::EncodePointDelta(m_sink, lastUpt, upt);
lastUpt = upt;
}
}
void operator()(std::vector<m2::PointD> const & points, char const * /* name */ = nullptr)
{
SavePointsSequence(points);
}
void operator()(MultiGeometry::LineT const & points, char const * /* name */ = nullptr)
{
SavePointsSequence(points);
geometry::Altitude lastAltitude = geometry::kDefaultAltitudeMeters;
for (auto const & point : points)
{
WriteVarInt(m_sink, point.GetAltitude() - lastAltitude);
lastAltitude = point.GetAltitude();
}
}
void operator()(MultiGeometry const & geom, char const * /* name */ = nullptr)
{
/// @todo Update version if we want to save multi geometry into binary.
CHECK(!geom.m_lines.empty(), ());
(*this)(geom.m_lines[0]);
}
template <typename D>
std::enable_if_t<std::is_integral<D>::value> operator()(D d, char const * /* name */ = nullptr)
{
WriteToSink(m_sink, d);
}
template <typename R>
std::enable_if_t<!std::is_integral<R>::value> operator()(R const & r, char const * /* name */ = nullptr)
{
r.Visit(*this);
}
// Skip visiting. It is stored in the separate sections.
SKIP_VISITING(LocalizableString const &)
SKIP_VISITING(std::string const &)
SKIP_VISITING(std::vector<std::string> const &)
SKIP_VISITING(Properties const &)
private:
Sink & m_sink;
uint8_t const m_doubleBits;
};
template <typename Source>
class CategoryDeserializerVisitor
{
public:
explicit CategoryDeserializerVisitor(Source & source, uint8_t doubleBits) : m_source(source), m_doubleBits(doubleBits)
{}
void operator()(LocalizableStringIndex & index, char const * /* name */ = nullptr)
{
ReadLocalizableStringIndex(m_source, index);
}
void operator()(bool & b, char const * /* name */ = nullptr)
{
b = static_cast<bool>(ReadPrimitiveFromSource<uint8_t>(m_source));
}
void operator()(AccessRules & rules, char const * /* name */ = nullptr)
{
rules = static_cast<AccessRules>(ReadPrimitiveFromSource<uint8_t>(m_source));
}
void operator()(CompilationType & type, char const * /* name */ = nullptr)
{
type = static_cast<CompilationType>(ReadPrimitiveFromSource<uint8_t>(m_source));
}
void operator()(Timestamp & t, char const * /* name */ = nullptr)
{
auto const v = ReadVarUint<uint64_t, Source>(m_source);
t = FromSecondsSinceEpoch(v);
}
void operator()(TimestampMillis & t, char const * /* name */ = nullptr)
{
auto const v = ReadVarUint<uint64_t, Source>(m_source);
t = FromSecondsSinceEpoch(v / 1000);
}
void operator()(double & d, char const * /* name */ = nullptr)
{
auto const v = ReadVarUint<uint32_t, Source>(m_source);
d = Uint32ToDouble(v, kMinRating, kMaxRating, m_doubleBits);
}
void operator()(m2::PointD & pt, char const * /* name */ = nullptr) { pt = ReadPointD(m_source, m_doubleBits); }
void operator()(CategoryData & compilationData, char const * /* name */ = nullptr) { compilationData.Visit(*this); }
template <typename T>
void operator()(std::vector<T> & vs, char const * /* name */ = nullptr)
{
auto const sz = ReadVarUint<uint32_t, Source>(m_source);
vs.reserve(sz);
for (uint32_t i = 0; i < sz; ++i)
{
vs.emplace_back(T());
(*this)(vs.back());
}
}
template <typename D>
std::enable_if_t<std::is_integral<D>::value> operator()(D & d, char const * /* name */ = nullptr)
{
d = ReadPrimitiveFromSource<D>(m_source);
}
template <typename R>
std::enable_if_t<!std::is_integral<R>::value> operator()(R & r, char const * /* name */ = nullptr)
{
r.Visit(*this);
}
// Skip visiting. It is stored in the separate sections.
SKIP_VISITING(LocalizableString &)
SKIP_VISITING(std::string &)
SKIP_VISITING(std::vector<std::string> &)
SKIP_VISITING(Properties &)
SKIP_VISITING(std::vector<BookmarkData> &)
SKIP_VISITING(std::vector<TrackData> &)
private:
Source & m_source;
uint8_t const m_doubleBits;
};
template <typename Source>
class BookmarkDeserializerVisitor
{
public:
explicit BookmarkDeserializerVisitor(Source & source, uint8_t doubleBits) : m_source(source), m_doubleBits(doubleBits)
{}
void operator()(LocalizableStringIndex & index, char const * /* name */ = nullptr)
{
ReadLocalizableStringIndex(m_source, index);
}
void operator()(bool & b, char const * /* name */ = nullptr)
{
b = static_cast<bool>(ReadPrimitiveFromSource<uint8_t>(m_source));
}
void operator()(m2::PointD & pt, char const * /* name */ = nullptr) { pt = ReadPointD(m_source, m_doubleBits); }
void operator()(geometry::PointWithAltitude & pt, char const * /* name */ = nullptr)
{
pt.SetPoint(ReadPointD(m_source, m_doubleBits));
pt.SetAltitude(ReadVarInt<int32_t, Source>(m_source));
}
void operator()(double & d, char const * /* name */ = nullptr)
{
auto const v = ReadVarUint<uint32_t, Source>(m_source);
d = Uint32ToDouble(v, kMinLineWidth, kMaxLineWidth, m_doubleBits);
}
void operator()(Timestamp & t, char const * /* name */ = nullptr)
{
auto const v = ReadVarUint<uint64_t, Source>(m_source);
t = FromSecondsSinceEpoch(v);
}
void operator()(TimestampMillis & t, char const * /* name */ = nullptr)
{
auto const v = ReadVarUint<uint64_t, Source>(m_source);
t = FromSecondsSinceEpoch(v / 1000);
}
void operator()(PredefinedColor & color, char const * /* name */ = nullptr)
{
color = static_cast<PredefinedColor>(ReadPrimitiveFromSource<uint8_t>(m_source));
}
void operator()(AccessRules & rules, char const * /* name */ = nullptr)
{
rules = static_cast<AccessRules>(ReadPrimitiveFromSource<uint8_t>(m_source));
}
void operator()(BookmarkIcon & icon, char const * /* name */ = nullptr)
{
icon = static_cast<BookmarkIcon>(ReadPrimitiveFromSource<uint16_t>(m_source));
}
template <typename T>
void operator()(std::vector<T> & vs, char const * /* name */ = nullptr)
{
auto const sz = ReadVarUint<uint32_t, Source>(m_source);
vs.reserve(sz);
for (uint32_t i = 0; i < sz; ++i)
{
vs.emplace_back(T());
(*this)(vs.back());
}
}
template <class T>
void LoadPointsSequence(std::vector<T> & points)
{
auto const sz = ReadVarUint<uint32_t, Source>(this->m_source);
points.reserve(sz);
m2::PointU lastUpt = m2::PointU::Zero();
for (uint32_t i = 0; i < sz; ++i)
{
lastUpt = coding::DecodePointDelta(this->m_source, lastUpt);
points.emplace_back(PointUToPointD(lastUpt, m_doubleBits));
}
}
void operator()(std::vector<m2::PointD> & points, char const * /* name */ = nullptr) { LoadPointsSequence(points); }
void operator()(MultiGeometry::LineT & points, char const * /* name */ = nullptr)
{
LoadPointsSequence(points);
geometry::Altitude lastAltitude = geometry::kDefaultAltitudeMeters;
for (auto & point : points)
{
point.SetAltitude(lastAltitude + ReadVarInt<int32_t>(m_source));
lastAltitude = point.GetAltitude();
}
}
void operator()(MultiGeometry & geom, char const * /* name */ = nullptr)
{
/// @todo Update version if we want to save multi geometry into binary.
MultiGeometry::LineT line;
(*this)(line);
geom.m_lines.push_back(std::move(line));
}
template <typename D>
std::enable_if_t<std::is_integral<D>::value> operator()(D & d, char const * /* name */ = nullptr)
{
d = ReadPrimitiveFromSource<D>(m_source);
}
template <typename R>
std::enable_if_t<!std::is_integral<R>::value> operator()(R & r, char const * /* name */ = nullptr)
{
r.Visit(*this);
}
// Skip visiting. It is stored in the separate sections.
SKIP_VISITING(LocalizableString &)
SKIP_VISITING(std::string &)
SKIP_VISITING(std::vector<std::string> &)
SKIP_VISITING(Properties &)
private:
Source & m_source;
uint8_t const m_doubleBits;
};
template <typename Reader>
class DeserializedStringCollector
{
public:
explicit DeserializedStringCollector(coding::BlockedTextStorage<Reader> & textStorage) : m_textStorage(textStorage) {}
template <typename... OtherStrings>
void Collect(LocalizableStringIndex & index, LocalizableString & str, OtherStrings &... args)
{
if (!SwitchSubIndexIfNeeded(index))
return;
auto subIndex = index[m_counter];
for (auto const & p : subIndex)
str[p.first] = ExtractString(p.second);
m_counter++;
Collect(index, args...);
}
template <typename... OtherStrings>
void Collect(LocalizableStringIndex & index, std::string & str, OtherStrings &... args)
{
if (!SwitchSubIndexIfNeeded(index))
return;
auto subIndex = index[m_counter];
if (!subIndex.empty())
str = ExtractString(subIndex.begin()->second);
else
str = {};
m_counter++;
Collect(index, args...);
}
template <typename... OtherStrings>
void Collect(LocalizableStringIndex & index, std::vector<std::string> & stringsArray, OtherStrings &... args)
{
if (!SwitchSubIndexIfNeeded(index))
return;
auto subIndex = index[m_counter];
stringsArray.reserve(subIndex.size());
for (auto const & p : subIndex)
stringsArray.emplace_back(ExtractString(p.second));
m_counter++;
Collect(index, args...);
}
template <typename... OtherStrings>
void Collect(LocalizableStringIndex & index, Properties & properties, OtherStrings &... args)
{
if (!SwitchSubIndexIfNeeded(index))
return;
auto subIndex = index[m_counter];
auto const sz = static_cast<int8_t>(subIndex.size() / 2);
for (int8_t i = 0; i < sz; i++)
properties.insert(std::make_pair(ExtractString(subIndex[2 * i]), ExtractString(subIndex[2 * i + 1])));
m_counter++;
Collect(index, args...);
}
template <typename...>
void Collect(LocalizableStringIndex & index)
{}
private:
bool SwitchSubIndexIfNeeded(LocalizableStringIndex & index)
{
if (m_lastIndex != &index)
{
m_counter = 0;
m_lastIndex = &index;
}
return m_counter < index.size();
}
std::string ExtractString(uint32_t stringIndex) const
{
auto const stringsCount = m_textStorage.GetNumStrings();
if (stringIndex >= stringsCount)
return {};
return m_textStorage.ExtractString(stringIndex);
}
coding::BlockedTextStorage<Reader> & m_textStorage;
LocalizableStringIndex * m_lastIndex = nullptr;
size_t m_counter = 0;
};
} // namespace binary
} // namespace kml