diff --git a/kml/kml_tests/serdes_tests.cpp b/kml/kml_tests/serdes_tests.cpp index 626a76265c..13df3b66db 100644 --- a/kml/kml_tests/serdes_tests.cpp +++ b/kml/kml_tests/serdes_tests.cpp @@ -792,3 +792,46 @@ UNIT_TEST(Kml_Deserialization_From_Bin_V6_And_V7) TEST_EQUAL(dataFromBinV6, dataFromBinV7, ()); } + +UNIT_TEST(Kml_Ver_2_3) +{ + char const * data = R"( + + + + 2010-05-28T02:02:09Z + 2010-05-28T02:02:35Z + 2010-05-28T02:02:44Z + 2010-05-28T02:02:53Z + 2010-05-28T02:02:54Z + 2010-05-28T02:02:55Z + 2010-05-28T02:02:56Z + -122.207881 37.371915 156.000000 + -122.205712 37.373288 152.000000 + -122.204678 37.373939 147.000000 + -122.203572 37.374630 142.199997 + -122.203451 37.374706 141.800003 + -122.203329 37.374780 141.199997 + -122.203207 37.374857 140.199997 + + + + )"; + + kml::FileData fData; + try + { + MemReader reader(data, strlen(data)); + kml::DeserializerKml des(fData); + des.Deserialize(reader); + } + catch (kml::DeserializerKml::DeserializeException const & ex) + { + TEST(false, ("Exception raised", ex.Msg())); + } + + TEST_EQUAL(fData.m_tracksData.size(), 1, ()); + auto const & lines = fData.m_tracksData[0].m_geometry.m_lines; + TEST_EQUAL(lines.size(), 1, ()); + TEST_EQUAL(lines[0].size(), 7, ()); +} diff --git a/kml/serdes.cpp b/kml/serdes.cpp index c4e3c7e6d1..0e9d473737 100644 --- a/kml/serdes.cpp +++ b/kml/serdes.cpp @@ -30,6 +30,11 @@ std::string const kPair = "Pair"; std::string const kExtendedData = "ExtendedData"; std::string const kCompilation = "mwm:compilation"; +std::string const kCoordinates = "coordinates"; +std::string const kTrack = "Track"; +std::string const gxTrack = "gx:Track"; +std::string const gxCoord = "gx:coord"; + std::string const kKmlHeader = "\n" "\n" @@ -725,23 +730,27 @@ void KmlParser::SetOrigin(std::string const & s) m_org = pt; } -void KmlParser::ParseLineCoordinates(std::string const & s, char const * blockSeparator, - char const * coordSeparator) +void KmlParser::ParseAndAddPoint(MultiGeometry::LineT & line, std::string_view v, char const * separator) +{ + geometry::PointWithAltitude point; + if (ParsePointWithAltitude(v, separator, point)) + { + // We dont't expect vertical surfaces, so do not compare heights here. + // Will get a lot of duplicating points otherwise after import some user KMLs. + // https://github.com/organicmaps/organicmaps/issues/3895 + if (line.empty() || !AlmostEqualAbs(line.back().GetPoint(), point.GetPoint(), kMwmPointAccuracy)) + line.emplace_back(point); + } +} + +void KmlParser::ParseLineCoordinates(std::string const & s, char const * blockSeparator, char const * coordSeparator) { m_geometryType = GEOMETRY_TYPE_LINE; MultiGeometry::LineT line; strings::Tokenize(s, blockSeparator, [&](std::string_view v) { - geometry::PointWithAltitude point; - if (ParsePointWithAltitude(v, coordSeparator, point)) - { - // We dont't expect vertical surfaces, so do not compare heights here. - // Will get a lot of duplicating points otherwise after import some user KMLs. - // https://github.com/organicmaps/organicmaps/issues/3895 - if (line.empty() || !AlmostEqualAbs(line.back().GetPoint(), point.GetPoint(), kMwmPointAccuracy)) - line.emplace_back(point); - } + ParseAndAddPoint(line, v, coordSeparator); }); if (line.size() > 1) @@ -820,6 +829,11 @@ bool KmlParser::Push(std::string const & tag) m_categoryData = &m_compilationData; m_compilationData.m_accessRules = m_data.m_categoryData.m_accessRules; } + else if (IsProcessTrackTag()) + { + m_geometryType = GEOMETRY_TYPE_LINE; + m_geometry.m_lines.push_back({}); + } return true; } @@ -881,6 +895,12 @@ std::string const & KmlParser::GetTagFromEnd(size_t n) const return m_tags[m_tags.size() - n - 1]; } +bool KmlParser::IsProcessTrackTag() const +{ + size_t const n = m_tags.size(); + return n >= 3 && m_tags[n - 1] == kTrack && (m_tags[n - 2] == kPlacemark || m_tags[n - 3] == kPlacemark); +} + void KmlParser::Pop(std::string const & tag) { ASSERT_EQUAL(m_tags.back(), tag, ()); @@ -968,6 +988,14 @@ void KmlParser::Pop(std::string const & tag) m_data.m_compilationsData.push_back(std::move(m_compilationData)); m_categoryData = &m_data.m_categoryData; } + else if (IsProcessTrackTag()) + { + // Simple line validation. + auto & lines = m_geometry.m_lines; + ASSERT(!lines.empty(), ()); + if (lines.back().size() < 2) + lines.pop_back(); + } m_tags.pop_back(); } @@ -985,6 +1013,20 @@ void KmlParser::CharData(std::string value) std::string const pppTag = count > 3 ? m_tags[count - 4] : std::string(); std::string const ppppTag = count > 4 ? m_tags[count - 5] : std::string(); + auto const TrackTag = [this, &prevTag, &currTag, &value]() + { + if (prevTag != kTrack) + return false; + + if (currTag == "coord") + { + auto & lines = m_geometry.m_lines; + ASSERT(!lines.empty(), ()); + ParseAndAddPoint(lines.back(), value, " "); + } + return true; + }; + if (prevTag == kDocument) { if (currTag == "name") @@ -1158,19 +1200,23 @@ void KmlParser::CharData(std::string value) { if (prevTag == "Point") { - if (currTag == "coordinates") + if (currTag == kCoordinates) SetOrigin(value); } else if (prevTag == "LineString") { - if (currTag == "coordinates") + if (currTag == kCoordinates) ParseLineCoordinates(value, " \n\r\t", ","); } - else if (prevTag == "gx:Track") + else if (prevTag == gxTrack) { - if (currTag == "gx:coord") + if (currTag == gxCoord) ParseLineCoordinates(value, "\n\r\t", " "); } + else if (TrackTag()) + { + // noop + } else if (prevTag == kExtendedData) { if (currTag == "mwm:scale") @@ -1239,68 +1285,75 @@ void KmlParser::CharData(std::string value) { if (prevTag == "Point") { - if (currTag == "coordinates") + if (currTag == kCoordinates) SetOrigin(value); } else if (prevTag == "LineString") { - if (currTag == "coordinates") + if (currTag == kCoordinates) ParseLineCoordinates(value, " \n\r\t", ","); } - else if (prevTag == "gx:Track") + else if (prevTag == gxTrack) { - if (currTag == "gx:coord") + if (currTag == gxCoord) ParseLineCoordinates(value, "\n\r\t", " "); } } else if (ppTag == "gx:MultiTrack") { - if (prevTag == "gx:Track") + if (prevTag == gxTrack) { - if (currTag == "gx:coord") + if (currTag == gxCoord) ParseLineCoordinates(value, "\n\r\t", " "); } } - else if (pppTag == kPlacemark && ppTag == kExtendedData) + else if (pppTag == kPlacemark) { - if (currTag == "mwm:lang") + if (ppTag == kExtendedData) { - if (prevTag == "mwm:name" && m_attrCode >= 0) - m_name[m_attrCode] = value; - else if (prevTag == "mwm:description" && m_attrCode >= 0) - m_description[m_attrCode] = value; - else if (prevTag == "mwm:customName" && m_attrCode >= 0) - m_customName[m_attrCode] = value; - m_attrCode = StringUtf8Multilang::kUnsupportedLanguageCode; - } - else if (currTag == "mwm:value") - { - uint32_t i; - if (prevTag == "mwm:featureTypes") + if (currTag == "mwm:lang") { - auto const & c = classif(); - if (!c.HasTypesMapping()) - MYTHROW(DeserializerKml::DeserializeException, ("Types mapping is not loaded.")); - auto const type = c.GetTypeByReadableObjectName(value); - if (c.IsTypeValid(type)) + if (prevTag == "mwm:name" && m_attrCode >= 0) + m_name[m_attrCode] = value; + else if (prevTag == "mwm:description" && m_attrCode >= 0) + m_description[m_attrCode] = value; + else if (prevTag == "mwm:customName" && m_attrCode >= 0) + m_customName[m_attrCode] = value; + m_attrCode = StringUtf8Multilang::kUnsupportedLanguageCode; + } + else if (currTag == "mwm:value") + { + uint32_t i; + if (prevTag == "mwm:featureTypes") { - auto const typeInd = c.GetIndexForType(type); - m_featureTypes.push_back(typeInd); + auto const & c = classif(); + if (!c.HasTypesMapping()) + MYTHROW(DeserializerKml::DeserializeException, ("Types mapping is not loaded.")); + auto const type = c.GetTypeByReadableObjectName(value); + if (c.IsTypeValid(type)) + { + auto const typeInd = c.GetIndexForType(type); + m_featureTypes.push_back(typeInd); + } + } + else if (prevTag == "mwm:boundTracks" && strings::to_uint(value, i)) + { + m_boundTracks.push_back(static_cast(i)); + } + else if (prevTag == "mwm:nearestToponyms") + { + m_nearestToponyms.push_back(value); + } + else if (prevTag == "mwm:properties" && !m_attrKey.empty()) + { + m_properties[m_attrKey] = value; + m_attrKey.clear(); } } - else if (prevTag == "mwm:boundTracks" && strings::to_uint(value, i)) - { - m_boundTracks.push_back(static_cast(i)); - } - else if (prevTag == "mwm:nearestToponyms") - { - m_nearestToponyms.push_back(value); - } - else if (prevTag == "mwm:properties" && !m_attrKey.empty()) - { - m_properties[m_attrKey] = value; - m_attrKey.clear(); - } + } + else if (ppTag == "MultiTrack" && TrackTag()) + { + // noop } } } diff --git a/kml/serdes.hpp b/kml/serdes.hpp index 99064bd5b6..7694efe30f 100644 --- a/kml/serdes.hpp +++ b/kml/serdes.hpp @@ -7,12 +7,9 @@ #include "coding/writer.hpp" #include "geometry/point2d.hpp" -#include "geometry/point_with_altitude.hpp" #include "base/exception.hpp" -#include "base/stl_helpers.hpp" -#include #include namespace kml @@ -67,17 +64,24 @@ class KmlParser { public: explicit KmlParser(FileData & data); + + /// @name Parser callback functions. + /// @{ bool Push(std::string const & name); void AddAttr(std::string const & attr, std::string const & value); - bool IsValidAttribute(std::string const & type, std::string const & value, - std::string const & attrInLowerCase) const; - std::string const & GetTagFromEnd(size_t n) const; void Pop(std::string const & tag); void CharData(std::string value); + /// @} + + bool IsValidAttribute(std::string const & 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; + enum GeometryType { GEOMETRY_TYPE_UNKNOWN, @@ -87,8 +91,9 @@ private: void ResetPoint(); void SetOrigin(std::string const & s); - void ParseLineCoordinates(std::string const & s, char const * blockSeparator, - char const * coordSeparator); + static void ParseAndAddPoint(MultiGeometry::LineT & line, std::string_view v, char const * separator); + void ParseLineCoordinates(std::string const & s, char const * blockSeparator, char const * coordSeparator); + bool MakeValid(); void ParseColor(std::string const &value); bool GetColorForStyle(std::string const & styleUrl, uint32_t & color) const;