diff --git a/data/test_data/gpx/export_test.gpx b/data/test_data/gpx/export_test.gpx
index 87e3a5305..d757f19bf 100644
--- a/data/test_data/gpx/export_test.gpx
+++ b/data/test_data/gpx/export_test.gpx
@@ -15,6 +15,15 @@
<&"]]>
<&"]]>
+
+ #FF00FF00
+
+
+
+ Point with color
+
+ #FFFFC800
+
Some random route
diff --git a/data/test_data/gpx/point_with_predefined_color_1.gpx b/data/test_data/gpx/point_with_predefined_color_1.gpx
new file mode 100644
index 000000000..32b0ad0f9
--- /dev/null
+++ b/data/test_data/gpx/point_with_predefined_color_1.gpx
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Point 1
+ Point 1
+
+
\ No newline at end of file
diff --git a/data/test_data/gpx/point_with_predefined_color_2.gpx b/data/test_data/gpx/point_with_predefined_color_2.gpx
new file mode 100644
index 000000000..7cc6e8648
--- /dev/null
+++ b/data/test_data/gpx/point_with_predefined_color_2.gpx
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Point 1
+ Point 1
+
+ #FF0066CC
+
+
+
\ No newline at end of file
diff --git a/libs/kml/color_parser.cpp b/libs/kml/color_parser.cpp
index 2e66603ae..247457e4d 100644
--- a/libs/kml/color_parser.cpp
+++ b/libs/kml/color_parser.cpp
@@ -1,6 +1,7 @@
#include "color_parser.hpp"
#include "coding/hex.hpp"
+#include "types.hpp"
#include "base/string_utils.hpp"
@@ -31,6 +32,97 @@ std::optional ParseHexColor(std::string_view c)
}
}
+std::tuple 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({{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::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 buildRGBAToPredefined()
+{
+ auto res = std::array();
+ 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::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 ParseGarminColor(std::string_view c)
diff --git a/libs/kml/color_parser.hpp b/libs/kml/color_parser.hpp
index f4bb86633..6fb8cb8e4 100644
--- a/libs/kml/color_parser.hpp
+++ b/libs/kml/color_parser.hpp
@@ -4,6 +4,8 @@
#include
#include
+#include "types.hpp"
+
namespace kml
{
@@ -18,4 +20,7 @@ std::optional ParseHexColor(std::string_view c);
std::optional ParseGarminColor(std::string_view c);
std::optional ParseOSMColor(std::string_view c);
+PredefinedColor MapPredefinedColor(uint32_t rgba);
+std::string_view MapGarminColor(uint32_t rgba);
+
} // namespace kml
diff --git a/libs/kml/kml_tests/gpx_tests.cpp b/libs/kml/kml_tests/gpx_tests.cpp
index 1d77396fe..7f803623f 100644
--- a/libs/kml/kml_tests/gpx_tests.cpp
+++ b/libs/kml/kml_tests/gpx_tests.cpp
@@ -1,5 +1,6 @@
#include "testing/testing.hpp"
+#include "kml/color_parser.hpp"
#include "kml/serdes_common.hpp"
#include "kml/serdes_gpx.hpp"
@@ -38,9 +39,8 @@ static std::string ReadFile(char const * testFile)
return sourceFileText;
}
-static std::string ReadFileAndSerialize(char const * testFile)
+static std::string Serialize(kml::FileData const & dataFromFile)
{
- kml::FileData const dataFromFile = LoadGpxFromFile(testFile);
std::string resultBuffer;
MemWriter sink(resultBuffer);
kml::gpx::SerializerGpx ser(dataFromFile);
@@ -48,6 +48,12 @@ static std::string ReadFileAndSerialize(char const * testFile)
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);
@@ -323,6 +329,20 @@ UNIT_TEST(Empty)
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");
@@ -394,11 +414,11 @@ UNIT_TEST(ParseFromString)
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), ());
+ 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
diff --git a/libs/kml/serdes_gpx.cpp b/libs/kml/serdes_gpx.cpp
index 705a15d29..fe9c2dbf2 100644
--- a/libs/kml/serdes_gpx.cpp
+++ b/libs/kml/serdes_gpx.cpp
@@ -80,11 +80,10 @@ bool GpxParser::MakeValid()
// Set default name.
if (m_name.empty())
m_name = kml::PointToLineString(m_org);
-
- // Set default pin.
- if (m_predefinedColor == PredefinedColor::None)
+ if (m_color != kInvalidColor)
+ m_predefinedColor = MapPredefinedColor(m_color);
+ else
m_predefinedColor = PredefinedColor::Red;
-
return true;
}
return false;
@@ -428,61 +427,6 @@ std::string GpxParser::BuildDescription() const
return m_description + "\n\n" + m_comment;
}
-std::tuple 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({{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::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;
-}
-
namespace
{
@@ -524,6 +468,16 @@ void SaveCategoryData(Writer & writer, CategoryData const & categoryData)
writer << "\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);
@@ -544,6 +498,14 @@ void SaveBookmarkData(Writer & writer, BookmarkData const & bookmarkData)
SaveStringWithCDATA(writer, *description);
writer << "\n";
}
+ if (auto const color = BookmarkColor(bookmarkData); color != kInvalidColor)
+ {
+ writer << kIndent2 << "\n";
+ writer << kIndent4 << "#";
+ SaveColorToARGB(writer, color);
+ writer << "\n";
+ writer << kIndent2 << "\n";
+ }
writer << "\n";
}
@@ -583,7 +545,7 @@ void SaveTrackData(Writer & writer, TrackData const & trackData)
{
writer << kIndent2 << "\n";
writer << kIndent4 << "";
- writer << MapGarminColor(color);
+ writer << kml::MapGarminColor(color);
writer << "\n";
writer << kIndent4 << "";
SaveColorToRGB(writer, color);
diff --git a/libs/kml/serdes_gpx.hpp b/libs/kml/serdes_gpx.hpp
index d5c42a7dc..06022ad24 100644
--- a/libs/kml/serdes_gpx.hpp
+++ b/libs/kml/serdes_gpx.hpp
@@ -106,8 +106,6 @@ private:
std::string BuildDescription() const;
};
-std::string_view MapGarminColor(uint32_t rgba);
-
} // namespace gpx
class DeserializerGpx
diff --git a/libs/map/bookmark.cpp b/libs/map/bookmark.cpp
index 20c8f6d01..858485fb5 100644
--- a/libs/map/bookmark.cpp
+++ b/libs/map/bookmark.cpp
@@ -58,6 +58,7 @@ std::string GetBookmarkIconType(kml::BookmarkIcon const & icon)
std::string const kCustomImageProperty = "CustomImage";
std::string const kHasElevationProfileProperty = "has_elevation_profile";
+int constexpr kInvalidColor = 0;
} // namespace
Bookmark::Bookmark(m2::PointD const & ptOrg) : Base(ptOrg, UserMark::BOOKMARK), m_groupId(kml::kInvalidMarkGroupId)
@@ -185,6 +186,7 @@ void Bookmark::SetColor(kml::PredefinedColor color)
{
SetDirty();
m_data.m_color.m_predefinedColor = color;
+ m_data.m_color.m_rgba = kInvalidColor;
}
std::string Bookmark::GetPreferredName() const