Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
40
libs/kml/CMakeLists.txt
Normal file
40
libs/kml/CMakeLists.txt
Normal 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
214
libs/kml/color_parser.cpp
Normal 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
26
libs/kml/color_parser.hpp
Normal 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
|
||||
89
libs/kml/header_binary.hpp
Normal file
89
libs/kml/header_binary.hpp
Normal 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
|
||||
8
libs/kml/kmb_to_kml/CMakeLists.txt
Normal file
8
libs/kml/kmb_to_kml/CMakeLists.txt
Normal 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)
|
||||
60
libs/kml/kmb_to_kml/kmb_to_kml.cpp
Normal file
60
libs/kml/kmb_to_kml/kmb_to_kml.cpp
Normal 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;
|
||||
}
|
||||
15
libs/kml/kml_tests/CMakeLists.txt
Normal file
15
libs/kml/kml_tests/CMakeLists.txt
Normal 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
|
||||
)
|
||||
19
libs/kml/kml_tests/color_parser_tests.cpp
Normal file
19
libs/kml/kml_tests/color_parser_tests.cpp
Normal 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"), ());
|
||||
}
|
||||
431
libs/kml/kml_tests/gpx_tests.cpp
Normal file
431
libs/kml/kml_tests/gpx_tests.cpp
Normal 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
|
||||
159
libs/kml/kml_tests/minzoom_quadtree_tests.cpp
Normal file
159
libs/kml/kml_tests/minzoom_quadtree_tests.cpp
Normal 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(), ());
|
||||
}
|
||||
917
libs/kml/kml_tests/serdes_tests.cpp
Normal file
917
libs/kml/kml_tests/serdes_tests.cpp
Normal 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(), ());
|
||||
}
|
||||
}
|
||||
1478
libs/kml/kml_tests/tests_data.hpp
Normal file
1478
libs/kml/kml_tests/tests_data.hpp
Normal file
File diff suppressed because it is too large
Load diff
8
libs/kml/kml_to_kmb/CMakeLists.txt
Normal file
8
libs/kml/kml_to_kmb/CMakeLists.txt
Normal 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)
|
||||
78
libs/kml/kml_to_kmb/kml_to_kmb.cpp
Normal file
78
libs/kml/kml_to_kmb/kml_to_kmb.cpp
Normal 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;
|
||||
}
|
||||
171
libs/kml/minzoom_quadtree.hpp
Normal file
171
libs/kml/minzoom_quadtree.hpp
Normal 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
|
||||
36
libs/kml/pykmlib/CMakeLists.txt
Normal file
36
libs/kml/pykmlib/CMakeLists.txt
Normal 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 "")
|
||||
938
libs/kml/pykmlib/bindings.cpp
Normal file
938
libs/kml/pykmlib/bindings.cpp
Normal 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);
|
||||
}
|
||||
122
libs/kml/pykmlib/bindings_test.py
Normal file
122
libs/kml/pykmlib/bindings_test.py
Normal 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
13
libs/kml/pykmlib/setup.py
Normal 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
1416
libs/kml/serdes.cpp
Normal file
File diff suppressed because it is too large
Load diff
160
libs/kml/serdes.hpp
Normal file
160
libs/kml/serdes.hpp
Normal 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
|
||||
36
libs/kml/serdes_binary.cpp
Normal file
36
libs/kml/serdes_binary.cpp
Normal 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
411
libs/kml/serdes_binary.hpp
Normal 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
|
||||
88
libs/kml/serdes_binary_v8.hpp
Normal file
88
libs/kml/serdes_binary_v8.hpp
Normal 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
|
||||
79
libs/kml/serdes_common.cpp
Normal file
79
libs/kml/serdes_common.cpp
Normal 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
|
||||
31
libs/kml/serdes_common.hpp
Normal file
31
libs/kml/serdes_common.hpp
Normal 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
610
libs/kml/serdes_gpx.cpp
Normal 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
138
libs/kml/serdes_gpx.hpp
Normal 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
80
libs/kml/type_utils.cpp
Normal 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
141
libs/kml/type_utils.hpp
Normal 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
113
libs/kml/types.cpp
Normal 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
582
libs/kml/types.hpp
Normal 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
255
libs/kml/types_v3.hpp
Normal 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
127
libs/kml/types_v6.hpp
Normal 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
223
libs/kml/types_v7.hpp
Normal 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
160
libs/kml/types_v8.hpp
Normal 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
283
libs/kml/types_v8mm.hpp
Normal 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
11
libs/kml/types_v9.hpp
Normal 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
45
libs/kml/types_v9mm.hpp
Normal 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
744
libs/kml/visitors.hpp
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue