forked from organicmaps/organicmaps-tmp
[gpx] Add xml schema to color in gpx export
Signed-off-by: cyber-toad <the.cyber.toad@proton.me>
This commit is contained in:
parent
6b6b7d145e
commit
1f0a6760e0
7 changed files with 265 additions and 65 deletions
28
data/test_data/gpx/color_map_dst.gpx
Normal file
28
data/test_data/gpx/color_map_dst.gpx
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0"?>
|
||||
<gpx version="1.1" creator="Organic Maps" 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">
|
||||
<metadata>
|
||||
<name>new</name>
|
||||
<desc>Short description</desc>
|
||||
</metadata>
|
||||
<trk>
|
||||
<name>new red</name>
|
||||
<desc>description 1</desc>
|
||||
<extensions>
|
||||
<gpxx:TrackExtension><gpxx:DisplayColor>DarkGray</gpxx:DisplayColor></gpxx:TrackExtension>
|
||||
<gpx_style:line><gpx_style:color>443344</gpx_style:color></gpx_style:line>
|
||||
<xsi:gpx><color>#FF443344</color></xsi:gpx>
|
||||
</extensions>
|
||||
<trkseg>
|
||||
<trkpt lat="48.209846" lon="16.376023">
|
||||
<ele>123</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="48.209855" lon="16.376066">
|
||||
<ele>123</ele>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
29
data/test_data/gpx/color_map_src.gpx
Normal file
29
data/test_data/gpx/color_map_src.gpx
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.topografix.com/GPX/gpx_style/0/2 http://www.topografix.com/GPX/gpx_style/0/2/gpx_style.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:gpx_style="http://www.topografix.com/GPX/gpx_style/0/2" version="1.1" creator="https://gpx.studio">
|
||||
<metadata>
|
||||
<name>new</name>
|
||||
<author>
|
||||
<name>gpx.studio</name>
|
||||
<link href="https://gpx.studio"></link>
|
||||
</author>
|
||||
<desc>Short description</desc>
|
||||
</metadata>
|
||||
<trk>
|
||||
<name>new red</name>
|
||||
<desc>description 1</desc>
|
||||
<type>Running</type>
|
||||
<extensions>
|
||||
<gpx_style:line>
|
||||
<color>443344</color>
|
||||
</gpx_style:line>
|
||||
</extensions>
|
||||
<trkseg>
|
||||
<trkpt lat="48.209846" lon="16.376023">
|
||||
<ele>123</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="48.209855" lon="16.376066">
|
||||
<ele>123</ele>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
|
@ -1,5 +1,9 @@
|
|||
<?xml version="1.0"?>
|
||||
<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1">
|
||||
<gpx version="1.1" creator="Organic Maps" 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">
|
||||
<metadata>
|
||||
<name>My route</name>
|
||||
<desc><![CDATA[You will see this in the document and can use reserved characters like < > & "]]></desc>
|
||||
|
@ -15,7 +19,9 @@
|
|||
<trk>
|
||||
<name>Some random route</name>
|
||||
<extensions>
|
||||
<color>00FF00</color>
|
||||
<gpxx:TrackExtension><gpxx:DisplayColor>Green</gpxx:DisplayColor></gpxx:TrackExtension>
|
||||
<gpx_style:line><gpx_style:color>00FF00</gpx_style:color></gpx_style:line>
|
||||
<xsi:gpx><color>#FF00FF00</color></xsi:gpx>
|
||||
</extensions>
|
||||
<trkseg>
|
||||
<trkpt lat="48.209846" lon="16.376023">
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<?xml version="1.0"?>
|
||||
<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1">
|
||||
<gpx version="1.1" creator="Organic Maps" 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">
|
||||
<metadata>
|
||||
</metadata>
|
||||
</gpx>
|
|
@ -11,7 +11,8 @@
|
|||
|
||||
namespace gpx_tests
|
||||
{
|
||||
static kml::FileData loadGpxFromString(std::string_view content) {
|
||||
static kml::FileData LoadGpxFromString(std::string_view content)
|
||||
{
|
||||
TEST_NO_THROW(
|
||||
{
|
||||
kml::FileData dataFromText;
|
||||
|
@ -20,34 +21,59 @@ static kml::FileData loadGpxFromString(std::string_view content) {
|
|||
}, ());
|
||||
}
|
||||
|
||||
static kml::FileData loadGpxFromFile(std::string const & file) {
|
||||
static kml::FileData LoadGpxFromFile(std::string const & file)
|
||||
{
|
||||
auto const fileName = GetPlatform().TestsDataPathForFile(file);
|
||||
std::string text;
|
||||
FileReader(fileName).ReadAsString(text);
|
||||
return loadGpxFromString(text);
|
||||
return LoadGpxFromString(text);
|
||||
}
|
||||
|
||||
void importExportCompare(char const * testFile)
|
||||
static std::string ReadFile(char const * testFile)
|
||||
{
|
||||
auto const fileName = GetPlatform().TestsDataPathForFile(testFile);
|
||||
std::string sourceFileText;
|
||||
FileReader(fileName).ReadAsString(sourceFileText);
|
||||
kml::FileData const dataFromFile = loadGpxFromFile(testFile);
|
||||
return sourceFileText;
|
||||
}
|
||||
|
||||
static std::string ReadFileAndSerialize(char const * testFile)
|
||||
{
|
||||
kml::FileData const dataFromFile = LoadGpxFromFile(testFile);
|
||||
std::string resultBuffer;
|
||||
MemWriter<decltype(resultBuffer)> sink(resultBuffer);
|
||||
kml::gpx::SerializerGpx ser(dataFromFile);
|
||||
ser.Serialize(sink);
|
||||
return resultBuffer;
|
||||
}
|
||||
|
||||
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");
|
||||
ImportExportCompare("test_data/gpx/export_test.gpx");
|
||||
}
|
||||
|
||||
UNIT_TEST(Gpx_ImportExportEmpty_Test)
|
||||
{
|
||||
importExportCompare("test_data/gpx/export_test_empty.gpx");
|
||||
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)
|
||||
|
@ -68,7 +94,7 @@ UNIT_TEST(Gpx_Test_Point_With_Valid_Timestamp)
|
|||
bookmarkData.m_color = {kml::PredefinedColor::Red, 0};
|
||||
data.m_bookmarksData.emplace_back(std::move(bookmarkData));
|
||||
|
||||
kml::FileData const dataFromText = loadGpxFromString(input);
|
||||
kml::FileData const dataFromText = LoadGpxFromString(input);
|
||||
|
||||
TEST_EQUAL(dataFromText, data, ());
|
||||
}
|
||||
|
@ -83,14 +109,14 @@ UNIT_TEST(Gpx_Test_Point_With_Invalid_Timestamp)
|
|||
</wpt>
|
||||
)";
|
||||
|
||||
kml::FileData const dataFromText = loadGpxFromString(input);
|
||||
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);
|
||||
kml::FileData const dataFromText = LoadGpxFromFile(fileName);
|
||||
auto const & lines = dataFromText.m_tracksData[0].m_geometry.m_lines;
|
||||
TEST_EQUAL(lines.size(), 2, ());
|
||||
{
|
||||
|
@ -124,7 +150,7 @@ UNIT_TEST(Gpx_Test_Track_Without_Timestamps)
|
|||
UNIT_TEST(Gpx_Test_Track_With_Timestamps)
|
||||
{
|
||||
auto const fileName = "test_data/gpx/track_with_timestamps.gpx";
|
||||
kml::FileData const dataFromText = loadGpxFromFile(fileName);
|
||||
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, ());
|
||||
|
@ -168,7 +194,7 @@ UNIT_TEST(Gpx_Altitude_Issues)
|
|||
</gpx>
|
||||
)";
|
||||
|
||||
kml::FileData const dataFromText = loadGpxFromString(input);
|
||||
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), ());
|
||||
|
@ -200,7 +226,7 @@ UNIT_TEST(Gpx_Timestamp_Issues)
|
|||
</gpx>
|
||||
)";
|
||||
|
||||
kml::FileData const dataFromText = loadGpxFromString(input);
|
||||
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"), ());
|
||||
|
@ -215,21 +241,21 @@ UNIT_TEST(Gpx_Timestamp_Issues)
|
|||
|
||||
UNIT_TEST(GoMap)
|
||||
{
|
||||
kml::FileData const dataFromFile = loadGpxFromFile("test_data/gpx/go_map.gpx");
|
||||
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");
|
||||
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");
|
||||
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, ());
|
||||
|
@ -238,14 +264,14 @@ UNIT_TEST(OsmTrack)
|
|||
|
||||
UNIT_TEST(TowerCollector)
|
||||
{
|
||||
kml::FileData const dataFromFile = loadGpxFromFile("test_data/gpx/tower_collector.gpx");
|
||||
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");
|
||||
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], ());
|
||||
|
@ -254,7 +280,7 @@ UNIT_TEST(PointsOnly)
|
|||
|
||||
UNIT_TEST(Route)
|
||||
{
|
||||
kml::FileData dataFromFile = loadGpxFromFile("test_data/gpx/route.gpx");
|
||||
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", ());
|
||||
|
@ -264,7 +290,7 @@ UNIT_TEST(Route)
|
|||
|
||||
UNIT_TEST(Color)
|
||||
{
|
||||
kml::FileData const dataFromFile = loadGpxFromFile("test_data/gpx/color.gpx");
|
||||
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;
|
||||
|
@ -276,7 +302,7 @@ UNIT_TEST(Color)
|
|||
|
||||
UNIT_TEST(MultiTrackNames)
|
||||
{
|
||||
kml::FileData dataFromFile = loadGpxFromFile("test_data/gpx/color.gpx");
|
||||
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], ());
|
||||
|
@ -287,14 +313,14 @@ UNIT_TEST(MultiTrackNames)
|
|||
|
||||
UNIT_TEST(Empty)
|
||||
{
|
||||
kml::FileData dataFromFile = loadGpxFromFile("test_data/gpx/empty.gpx");
|
||||
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(OsmandColor1)
|
||||
{
|
||||
kml::FileData const dataFromFile = loadGpxFromFile("test_data/gpx/osmand1.gpx");
|
||||
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, ());
|
||||
|
@ -305,7 +331,7 @@ UNIT_TEST(OsmandColor1)
|
|||
|
||||
UNIT_TEST(OsmandColor2)
|
||||
{
|
||||
kml::FileData const dataFromFile = loadGpxFromFile("test_data/gpx/osmand2.gpx");
|
||||
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, ());
|
||||
|
@ -330,7 +356,7 @@ d5
|
|||
</wpt>
|
||||
<wpt lat="1" lon="2"><name>5</name><cmt>qqq</cmt><desc>qqq</desc></wpt>
|
||||
)";
|
||||
kml::FileData const dataFromText = loadGpxFromString(input);
|
||||
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), ());
|
||||
|
@ -340,9 +366,35 @@ d5
|
|||
|
||||
UNIT_TEST(OpentracksColor)
|
||||
{
|
||||
kml::FileData dataFromFile = loadGpxFromFile("test_data/gpx/opentracks_color.gpx");
|
||||
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::gpx::MapGarminColor(0x008b8bff), ());
|
||||
TEST_EQUAL("White", kml::gpx::MapGarminColor(0xffffffff), ());
|
||||
TEST_EQUAL("DarkYellow", kml::gpx::MapGarminColor(0xb4b820ff), ());
|
||||
TEST_EQUAL("DarkYellow", kml::gpx::MapGarminColor(0xb6b820ff), ());
|
||||
TEST_EQUAL("DarkYellow", kml::gpx::MapGarminColor(0xb5b721ff), ());
|
||||
}
|
||||
|
||||
|
||||
} // namespace gpx_tests
|
||||
|
|
|
@ -33,10 +33,13 @@ std::string_view constexpr kEle = "ele";
|
|||
std::string_view constexpr kCmt = "cmt";
|
||||
std::string_view constexpr kTime = "time";
|
||||
|
||||
std::string_view constexpr kGpxHeader =
|
||||
"<?xml version=\"1.0\"?>\n"
|
||||
"<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" version=\"1.1\">\n";
|
||||
|
||||
std::string_view constexpr kGpxHeader = R"(<?xml version="1.0"?>
|
||||
<gpx version="1.1" creator="Organic Maps" 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;
|
||||
|
@ -131,52 +134,53 @@ std::string const & GpxParser::GetTagFromEnd(size_t n) const
|
|||
return m_tags[m_tags.size() - n - 1];
|
||||
}
|
||||
|
||||
void GpxParser::ParseColor(std::string const & value)
|
||||
std::optional<uint32_t> GpxParser::ParseColorFromHexString(std::string_view colorStr)
|
||||
{
|
||||
auto const colorBytes = FromHex(value);
|
||||
if (colorBytes.size() != 3)
|
||||
if (colorStr.empty())
|
||||
{
|
||||
LOG(LWARNING, ("Invalid color value", value));
|
||||
return;
|
||||
LOG(LWARNING, ("Invalid color value", colorStr));
|
||||
return {};
|
||||
}
|
||||
m_color = kml::ToRGBA(colorBytes[0], colorBytes[1], colorBytes[2], (char)255);
|
||||
if (colorStr.front() == '#')
|
||||
colorStr.remove_prefix(1);
|
||||
if (colorStr.size() != 6 && colorStr.size() != 8)
|
||||
{
|
||||
LOG(LWARNING, ("Invalid color value", colorStr));
|
||||
return {};
|
||||
}
|
||||
auto const colorBytes = FromHex(colorStr);
|
||||
switch (colorBytes.size())
|
||||
{
|
||||
case 3: return kml::ToRGBA(colorBytes[0], colorBytes[1], colorBytes[2], (char)255);
|
||||
case 4: return kml::ToRGBA(colorBytes[1], colorBytes[2], colorBytes[3], colorBytes[0]);
|
||||
default:
|
||||
LOG(LWARNING, ("Invalid color value", colorStr));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void GpxParser::ParseColor(std::string_view colorStr)
|
||||
{
|
||||
if (const auto parsed = ParseColorFromHexString(colorStr); parsed)
|
||||
m_color = parsed.value();
|
||||
}
|
||||
|
||||
// https://osmand.net/docs/technical/osmand-file-formats/osmand-gpx/. Supported colors: #AARRGGBB/#RRGGBB/AARRGGBB/RRGGBB
|
||||
void GpxParser::ParseOsmandColor(std::string const & value)
|
||||
{
|
||||
if (value.empty())
|
||||
{
|
||||
LOG(LWARNING, ("Empty color value"));
|
||||
auto const color = ParseColorFromHexString(value);
|
||||
if (!color)
|
||||
return;
|
||||
}
|
||||
std::string_view colorStr = value;
|
||||
if (colorStr.at(0) == '#')
|
||||
colorStr = colorStr.substr(1, colorStr.size() - 1);
|
||||
auto const colorBytes = FromHex(colorStr);
|
||||
uint32_t color;
|
||||
switch (colorBytes.size())
|
||||
{
|
||||
case 3:
|
||||
color = kml::ToRGBA(colorBytes[0], colorBytes[1], colorBytes[2], (char)255);
|
||||
break;
|
||||
case 4:
|
||||
color = kml::ToRGBA(colorBytes[1], colorBytes[2], colorBytes[3], colorBytes[0]);
|
||||
break;
|
||||
default:
|
||||
LOG(LWARNING, ("Invalid color value", value));
|
||||
return;
|
||||
}
|
||||
if (m_tags.size() > 2 && GetTagFromEnd(2) == gpx::kGpx)
|
||||
{
|
||||
m_globalColor = color;
|
||||
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;
|
||||
m_color = *color;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -465,6 +469,64 @@ std::string GpxParser::BuildDescription() const
|
|||
return m_description + "\n\n" + m_comment;
|
||||
}
|
||||
|
||||
std::tuple<int, int, int> ExtractRGB(uint32_t color)
|
||||
{
|
||||
return {(color >> 24) & 0xFF, (color >> 16) & 0xFF, (color >> 8) & 0xFF};
|
||||
}
|
||||
|
||||
int ColorDistance(uint32_t color1, uint32_t color2)
|
||||
{
|
||||
auto const [r1, g1, b1] = ExtractRGB(color1);
|
||||
auto const [r2, g2, b2] = ExtractRGB(color2);
|
||||
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 (const auto & [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;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
@ -483,6 +545,13 @@ void SaveColorToRGB(Writer & writer, uint32_t rgba)
|
|||
<< 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)
|
||||
{
|
||||
|
@ -555,9 +624,17 @@ void SaveTrackData(Writer & writer, TrackData const & trackData)
|
|||
}
|
||||
if (auto const color = TrackColor(trackData); color != kDefaultTrackColor)
|
||||
{
|
||||
writer << kIndent2 << "<extensions>\n" << kIndent4 << "<color>";
|
||||
writer << kIndent2 << "<extensions>\n";
|
||||
writer << kIndent4 << "<gpxx:TrackExtension><gpxx:DisplayColor>";
|
||||
writer << MapGarminColor(color);
|
||||
writer << "</gpxx:DisplayColor></gpxx:TrackExtension>\n";
|
||||
writer << kIndent4 << "<gpx_style:line><gpx_style:color>";
|
||||
SaveColorToRGB(writer, color);
|
||||
writer << "</color>\n" << kIndent2 << "</extensions>\n";
|
||||
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;
|
||||
|
|
|
@ -61,6 +61,7 @@ public:
|
|||
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
|
||||
|
@ -72,7 +73,7 @@ private:
|
|||
|
||||
void ResetPoint();
|
||||
bool MakeValid();
|
||||
void ParseColor(std::string const & value);
|
||||
void ParseColor(std::string_view colorStr);
|
||||
void ParseGarminColor(std::string const & value);
|
||||
void ParseOsmandColor(std::string const & value);
|
||||
bool IsValidCoordinatesPosition() const;
|
||||
|
@ -108,6 +109,9 @@ private:
|
|||
void ParseTimestamp(std::string const & value);
|
||||
std::string BuildDescription() const;
|
||||
};
|
||||
|
||||
std::string_view MapGarminColor(uint32_t rgba);
|
||||
|
||||
} // namespace gpx
|
||||
|
||||
class DeserializerGpx
|
||||
|
|
Loading…
Add table
Reference in a new issue