diff --git a/data/gpx_test_data/osmand1.gpx b/data/gpx_test_data/osmand1.gpx
new file mode 100644
index 0000000000..fab91de010
--- /dev/null
+++ b/data/gpx_test_data/osmand1.gpx
@@ -0,0 +1,300 @@
+
+
+
+ Mon 26 Jun 2023
+
+
+
+
+
+ car
+ 0
+
+
+
+
+ car
+ 71
+
+
+
+
+ Mon 26 Jun 2023
+
+
+ 162.3
+
+
+ 162.3
+
+
+ 162.4
+
+
+ 163
+
+
+ 163
+
+
+ 163
+
+
+ 163
+
+
+ 163
+
+
+ 163
+
+
+ 163
+
+
+ 163
+
+
+ 163.3
+
+
+ 163.3
+
+
+ 163.4
+
+
+ 163.5
+
+
+ 163.8
+
+
+ 164
+
+
+ 164.5
+
+
+ 165.2
+
+
+ 165.3
+
+
+ 165.9
+
+
+ 166.2
+
+
+ 166.7
+
+
+ 166.9
+
+
+ 167
+
+
+ 166.9
+
+
+ 166.7
+
+
+ 166.3
+
+
+ 166.2
+
+
+ 166.1
+
+
+ 166.1
+
+
+ 166.2
+
+
+ 166.2
+
+
+ 166.2
+
+
+ 167
+
+
+ 167.2
+
+
+ 167.5
+
+
+ 167.4
+
+
+ 167.2
+
+
+ 167
+
+
+ 167
+
+
+ 169
+
+
+ 170
+
+
+ 170.1
+
+
+ 170.8
+
+
+ 170.4
+
+
+ 170.3
+
+
+ 170.2
+
+
+ 170.1
+
+
+ 170
+
+
+ 169.9
+
+
+ 169.9
+
+
+ 169.8
+
+
+ 169.7
+
+
+ 169.5
+
+
+ 169
+
+
+ 168.8
+
+
+ 169
+
+
+ 169
+
+
+ 170.2
+
+
+ 170
+
+
+ 170.2
+
+
+ 170.7
+
+
+ 171
+
+
+ 172
+
+
+ 168
+
+
+ 163.5
+
+
+ 164.2
+
+
+ 163.8
+
+
+ 163.6
+
+
+ 163.5
+
+
+ 163
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+ true
+ 0.0
+ no_split
+ #ff7800
+ thin
+ solid
+
+
\ No newline at end of file
diff --git a/data/gpx_test_data/osmand2.gpx b/data/gpx_test_data/osmand2.gpx
new file mode 100644
index 0000000000..1a856e5da8
--- /dev/null
+++ b/data/gpx_test_data/osmand2.gpx
@@ -0,0 +1,45 @@
+
+
+
+ favorites-Testowa
+
+
+
+ 114
+
+ Jednostka Wojskowa Nr 4393 i 4395
+ Testowa
+
+ military_landuse
+ EPLY
+ Jednostka Wojskowa Nr 4393 i 4395
+ military
+ Brak określonego adresu
+ military_landuse
+ circle
+ #00ff00
+ Amenity:Jednostka Wojskowa Nr 4393 i 4395: military:military_landuse
+
+
+
+ 111
+
+ Ochotnicza Straż Pożarna w Kaszewach Kościelnych
+ Testowa
+
+ fire_station
+ Ochotnicza Straż Pożarna w Kaszewach Kościelnych
+ emergency
+ Brak określonego adresu
+ special_star
+ circle
+ #1010a0
+ Amenity:Ochotnicza Straz Pozarna w Kaszewach Koscielnych: emergency:fire_station
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/kml/kml_tests/gpx_tests.cpp b/kml/kml_tests/gpx_tests.cpp
index 9e97e8360f..8e42599624 100644
--- a/kml/kml_tests/gpx_tests.cpp
+++ b/kml/kml_tests/gpx_tests.cpp
@@ -159,4 +159,21 @@ UNIT_TEST(Empty)
TEST_EQUAL(0, dataFromFile.m_tracksData.size(), ());
}
+UNIT_TEST(OsmandColor1)
+{
+ kml::FileData const dataFromFile = loadGpxFromFile("gpx_test_data/osmand1.gpx");
+ uint32_t const expected = 0xFF7800FF;
+ TEST_EQUAL(expected, dataFromFile.m_tracksData[0].m_layers[0].m_color.m_rgba, ());
+}
+
+UNIT_TEST(OsmandColor2)
+{
+ kml::FileData const dataFromFile = loadGpxFromFile("gpx_test_data/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, ());
+}
+
+
diff --git a/kml/serdes_gpx.cpp b/kml/serdes_gpx.cpp
index 940e6eb74b..86b0376c1e 100644
--- a/kml/serdes_gpx.cpp
+++ b/kml/serdes_gpx.cpp
@@ -17,8 +17,6 @@ namespace kml
namespace gpx
{
-using namespace std::string_view_literals;
-
std::string_view constexpr kTrk = "trk";
std::string_view constexpr kTrkSeg = "trkseg";
std::string_view constexpr kRte = "rte";
@@ -27,9 +25,12 @@ 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";
+int constexpr kInvalidColor = 0;
std::string PointToString(m2::PointD const & org)
@@ -57,7 +58,7 @@ void GpxParser::ResetPoint()
m_description.clear();
m_org = {};
m_predefinedColor = PredefinedColor::None;
- m_color = 0;
+ m_color = kInvalidColor;
m_customName.clear();
m_trackLayers.clear();
m_geometry.Clear();
@@ -120,7 +121,6 @@ void GpxParser::AddAttr(std::string const & attr, std::string const & value)
else if (attr == "lon")
m_lon = stod(value);
}
-
}
std::string_view GpxParser::GetTagFromEnd(size_t n) const
@@ -140,6 +140,39 @@ void GpxParser::ParseColor(std::string const & value)
m_color = kml::ToRGBA(colorBytes[0], colorBytes[1], colorBytes[2], (char)255);
}
+// https://osmand.net/docs/technical/osmand-file-formats/osmand-gpx/ - "#AARRGGBB" or "#RRGGBB"
+void GpxParser::ParseOsmandColor(std::string const & value) {
+ if (value.empty())
+ {
+ LOG(LWARNING, ("Empty color value"));
+ return;
+ }
+ auto const colorBytes = FromHex(value.substr(1, value.size() - 1));
+ 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 (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;
+ }
+}
// Garmin extensions spec: https://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd
// Color mapping: https://help.locusmap.eu/topic/extend-garmin-gpx-compatibilty
@@ -165,7 +198,8 @@ void GpxParser::ParseGarminColor(std::string const & v)
{"Transparent", "ff0000"}
};
auto const it = kGarminToHex.find(v);
- if (it != kGarminToHex.end()) {
+ if (it != kGarminToHex.end())
+ {
return ParseColor(it->second);
}
else
@@ -181,7 +215,7 @@ void GpxParser::Pop(std::string_view tag)
if (tag == gpx::kTrkPt || tag == gpx::kRtePt)
{
- m2::Point p = mercator::FromLatLon(m_lat, m_lon);
+ m2::PointD p = mercator::FromLatLon(m_lat, m_lon);
if (m_line.empty() || !AlmostEqualAbs(m_line.back().GetPoint(), p, kMwmPointAccuracy))
m_line.emplace_back(std::move(p));
}
@@ -217,7 +251,12 @@ void GpxParser::Pop(std::string_view tag)
{
TrackLayer layer;
layer.m_lineWidth = kml::kDefaultTrackWidth;
- layer.m_color.m_rgba = (m_color != 0 ? m_color : kml::kDefaultTrackColor);
+ 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;
m_trackLayers.push_back(std::move(layer));
TrackData data;
@@ -272,10 +311,12 @@ void GpxParser::CharData(std::string value)
else if (currTag == gpx::kDesc)
m_categoryData->m_description[kml::kDefaultLang] = value;
}
- if (currTag == gpx::kColor)
- ParseColor(value);
- else if (currTag == gpx::kGarminColor)
+ if (currTag == gpx::kGarminColor)
ParseGarminColor(value);
+ else if (currTag == gpx::kOsmandColor)
+ ParseOsmandColor(value);
+ else if (currTag == gpx::kColor)
+ ParseColor(value);
}
}
} // namespace gpx
diff --git a/kml/serdes_gpx.hpp b/kml/serdes_gpx.hpp
index 05f4973391..3f85a1fb7c 100644
--- a/kml/serdes_gpx.hpp
+++ b/kml/serdes_gpx.hpp
@@ -42,6 +42,7 @@ private:
bool MakeValid();
void ParseColor(std::string const & value);
void ParseGarminColor(std::string const & value);
+ void ParseOsmandColor(std::string const & value);
FileData & m_data;
CategoryData m_compilationData;
@@ -51,6 +52,7 @@ private:
GeometryType m_geometryType;
MultiGeometry m_geometry;
uint32_t m_color;
+ uint32_t m_globalColor; // To support OSMAnd extensions with single color per GPX file
LocalizableString m_name;
LocalizableString m_description;