forked from organicmaps/organicmaps
[editor] New XMLFeature methods.
This commit is contained in:
parent
daa4280f67
commit
0120cb4a24
3 changed files with 110 additions and 95 deletions
|
@ -135,61 +135,7 @@ UNIT_TEST(XMLFeature_IsArea)
|
|||
TEST(!XMLFeature(node).IsArea(), ());
|
||||
}
|
||||
|
||||
// UNIT_TEST(XMLFeature_FromXml)
|
||||
// {
|
||||
// auto const srcString = R"(<?xml version="1.0"?>
|
||||
// <node
|
||||
// lat="55.7978998"
|
||||
// lon="37.474528"
|
||||
// timestamp="2015-11-27T21:13:32Z">
|
||||
// <tag
|
||||
// k="name"
|
||||
// v="Gorki Park" />
|
||||
// <tag
|
||||
// k="name:en"
|
||||
// v="Gorki Park" />
|
||||
// <tag
|
||||
// k="name:ru"
|
||||
// v="Парк Горького" />
|
||||
// <tag
|
||||
// k="addr:housenumber"
|
||||
// v="10" />
|
||||
// <tag
|
||||
// k="opening_hours"
|
||||
// v="Mo-Fr 08:15-17:30" />
|
||||
// <tag
|
||||
// k="amenity"
|
||||
// v="atm" />
|
||||
// </node>
|
||||
// )";
|
||||
|
||||
// XMLFeature feature(srcString);
|
||||
|
||||
// stringstream sstr;
|
||||
// feature.Save(sstr);
|
||||
// TEST_EQUAL(srcString, sstr.str(), ());
|
||||
|
||||
// TEST(feature.HasKey("opening_hours"), ());
|
||||
// TEST(feature.HasKey("lat"), ());
|
||||
// TEST(feature.HasKey("lon"), ());
|
||||
// TEST(!feature.HasKey("FooBarBaz"), ());
|
||||
|
||||
// TEST_EQUAL(feature.GetHouse(), "10", ());
|
||||
// TEST_EQUAL(feature.GetCenter(), MercatorBounds::FromLatLon(55.7978998, 37.4745280), ());
|
||||
// TEST_EQUAL(feature.GetName(), "Gorki Park", ());
|
||||
// TEST_EQUAL(feature.GetName("default"), "Gorki Park", ());
|
||||
// TEST_EQUAL(feature.GetName("en"), "Gorki Park", ());
|
||||
// TEST_EQUAL(feature.GetName("ru"), "Парк Горького", ());
|
||||
// TEST_EQUAL(feature.GetName("No such language"), "", ());
|
||||
|
||||
// TEST_EQUAL(feature.GetTagValue("opening_hours"), "Mo-Fr 08:15-17:30", ());
|
||||
// TEST_EQUAL(feature.GetTagValue("amenity"), "atm", ());
|
||||
// TEST_EQUAL(my::TimestampToString(feature.GetModificationTime()), "2015-11-27T21:13:32Z", ());
|
||||
// }
|
||||
|
||||
UNIT_TEST(XMLFeature_ForEachName)
|
||||
{
|
||||
auto const srcString = R"(<?xml version="1.0"?>
|
||||
auto const kTestNode = R"(<?xml version="1.0"?>
|
||||
<node lat="55.7978998" lon="37.474528" timestamp="2015-11-27T21:13:32Z">
|
||||
<tag k="name" v="Gorki Park" />
|
||||
<tag k="name:en" v="Gorki Park" />
|
||||
|
@ -200,7 +146,35 @@ UNIT_TEST(XMLFeature_ForEachName)
|
|||
</node>
|
||||
)";
|
||||
|
||||
XMLFeature feature(srcString);
|
||||
UNIT_TEST(XMLFeature_FromXml)
|
||||
{
|
||||
XMLFeature feature(kTestNode);
|
||||
|
||||
stringstream sstr;
|
||||
feature.Save(sstr);
|
||||
TEST_EQUAL(kTestNode, sstr.str(), ());
|
||||
|
||||
TEST(feature.HasKey("opening_hours"), ());
|
||||
TEST(feature.HasKey("lat"), ());
|
||||
TEST(feature.HasKey("lon"), ());
|
||||
TEST(!feature.HasKey("FooBarBaz"), ());
|
||||
|
||||
TEST_EQUAL(feature.GetHouse(), "10", ());
|
||||
TEST_EQUAL(feature.GetCenter(), ms::LatLon(55.7978998, 37.4745280), ());
|
||||
TEST_EQUAL(feature.GetName(), "Gorki Park", ());
|
||||
TEST_EQUAL(feature.GetName("default"), "Gorki Park", ());
|
||||
TEST_EQUAL(feature.GetName("en"), "Gorki Park", ());
|
||||
TEST_EQUAL(feature.GetName("ru"), "Парк Горького", ());
|
||||
TEST_EQUAL(feature.GetName("No such language"), "", ());
|
||||
|
||||
TEST_EQUAL(feature.GetTagValue("opening_hours"), "Mo-Fr 08:15-17:30", ());
|
||||
TEST_EQUAL(feature.GetTagValue("amenity"), "atm", ());
|
||||
TEST_EQUAL(my::TimestampToString(feature.GetModificationTime()), "2015-11-27T21:13:32Z", ());
|
||||
}
|
||||
|
||||
UNIT_TEST(XMLFeature_ForEachName)
|
||||
{
|
||||
XMLFeature feature(kTestNode);
|
||||
map<string, string> names;
|
||||
|
||||
feature.ForEachName([&names](string const & lang, string const & name)
|
||||
|
@ -212,3 +186,29 @@ UNIT_TEST(XMLFeature_ForEachName)
|
|||
{"default", "Gorki Park"}, {"en", "Gorki Park"}, {"ru", "Парк Горького"}}),
|
||||
());
|
||||
}
|
||||
|
||||
auto const kTestNodeWay = R"(<?xml version="1.0"?>
|
||||
<osm>
|
||||
<node id="4" lat="55.7978998" lon="37.474528" timestamp="2015-11-27T21:13:32Z"/>
|
||||
<node id="5" lat="55.7977777" lon="37.474528" timestamp="2015-11-27T21:13:33Z"/>
|
||||
<way id="3" timestamp="2015-11-27T21:13:34Z">
|
||||
<nd ref="4"/>
|
||||
<nd ref="5"/>
|
||||
<tag k="hi" v="test"/>
|
||||
</way>
|
||||
</osm>
|
||||
)";
|
||||
|
||||
|
||||
UNIT_TEST(XMLFeature_FromOSM)
|
||||
{
|
||||
TEST_ANY_THROW(XMLFeature::FromOSM(""), ());
|
||||
TEST_ANY_THROW(XMLFeature::FromOSM("This is not XML"), ());
|
||||
TEST_ANY_THROW(XMLFeature::FromOSM("<?xml version=\"1.0\"?>"), ());
|
||||
TEST_NO_THROW(XMLFeature::FromOSM("<?xml version=\"1.0\"?><osm></osm>"), ());
|
||||
TEST_ANY_THROW(XMLFeature::FromOSM("<?xml version=\"1.0\"?><osm><node lat=\"11.11\"/></osm>"), ());
|
||||
vector<XMLFeature> features;
|
||||
TEST_NO_THROW(features = XMLFeature::FromOSM(kTestNodeWay), ());
|
||||
TEST_EQUAL(3, features.size(), ());
|
||||
TEST_EQUAL(features[2].GetTagValue("hi"), "test", ());
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "editor/xml_feature.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/macros.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
#include "base/timer.hpp"
|
||||
|
@ -31,37 +30,25 @@ ms::LatLon PointFromLatLon(pugi::xml_node const & node)
|
|||
{
|
||||
ms::LatLon ll;
|
||||
if (!strings::to_double(node.attribute("lat").value(), ll.lat))
|
||||
{
|
||||
MYTHROW(editor::XMLFeatureNoLatLonError,
|
||||
("Can't parse lat attribute: " + string(node.attribute("lat").value())));
|
||||
}
|
||||
MYTHROW(editor::NoLatLon, ("Can't parse lat attribute: " + string(node.attribute("lat").value())));
|
||||
if (!strings::to_double(node.attribute("lon").value(), ll.lon))
|
||||
{
|
||||
MYTHROW(editor::XMLFeatureNoLatLonError,
|
||||
("Can't parse lon attribute: " + string(node.attribute("lon").value())));
|
||||
}
|
||||
MYTHROW(editor::NoLatLon, ("Can't parse lon attribute: " + string(node.attribute("lon").value())));
|
||||
return ll;
|
||||
}
|
||||
|
||||
void ValidateNode(pugi::xml_node const & node)
|
||||
void ValidateElement(pugi::xml_node const & nodeOrWay)
|
||||
{
|
||||
if (!node)
|
||||
MYTHROW(editor::XMLFeatureNoNodeError, ("Document has no node"));
|
||||
if (!nodeOrWay)
|
||||
MYTHROW(editor::InvalidXML, ("Document has no valid root element."));
|
||||
|
||||
// Check if point can be parsed. Throws if it's can't.
|
||||
UNUSED_VALUE(PointFromLatLon(node));
|
||||
string const type = nodeOrWay.name();
|
||||
if (type == kNodeType)
|
||||
UNUSED_VALUE(PointFromLatLon(nodeOrWay));
|
||||
else if (type != kWayType)
|
||||
MYTHROW(editor::InvalidXML, ("XMLFeature does not support root tag", type));
|
||||
|
||||
if (!node.attribute(kTimestamp))
|
||||
MYTHROW(editor::XMLFeatureNoTimestampError, ("Node has no timestamp attribute"));
|
||||
}
|
||||
|
||||
void ValidateWay(pugi::xml_node const & way)
|
||||
{
|
||||
if (!way)
|
||||
MYTHROW(editor::XMLFeatureNoNodeError, ("Document has no node"));
|
||||
|
||||
if (!way.attribute(kTimestamp))
|
||||
MYTHROW(editor::XMLFeatureNoTimestampError, ("Way has no timestamp attribute"));
|
||||
if (!nodeOrWay.attribute(kTimestamp))
|
||||
MYTHROW(editor::NoTimestamp, ("Node has no timestamp attribute"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -80,23 +67,20 @@ XMLFeature::XMLFeature(Type const type)
|
|||
XMLFeature::XMLFeature(string const & xml)
|
||||
{
|
||||
m_document.load(xml.data());
|
||||
auto const r = GetRootNode();
|
||||
r.name() == kNodeType ? ValidateNode(r) : ValidateWay(r);
|
||||
ValidateElement(GetRootNode());
|
||||
}
|
||||
|
||||
XMLFeature::XMLFeature(pugi::xml_document const & xml)
|
||||
{
|
||||
m_document.reset(xml);
|
||||
auto const r = GetRootNode();
|
||||
r.name() == kNodeType ? ValidateNode(r) : ValidateWay(r);
|
||||
ValidateElement(GetRootNode());
|
||||
}
|
||||
|
||||
XMLFeature::XMLFeature(pugi::xml_node const & xml)
|
||||
{
|
||||
m_document.reset();
|
||||
m_document.append_copy(xml);
|
||||
auto const r = GetRootNode();
|
||||
r.name() == kNodeType ? ValidateNode(r) : ValidateWay(r);
|
||||
ValidateElement(GetRootNode());
|
||||
}
|
||||
|
||||
bool XMLFeature::operator==(XMLFeature const & other) const
|
||||
|
@ -104,11 +88,33 @@ bool XMLFeature::operator==(XMLFeature const & other) const
|
|||
return ToOSMString() == other.ToOSMString();
|
||||
}
|
||||
|
||||
vector<XMLFeature> XMLFeature::FromOSM(string const & osmXml)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
if (doc.load_string(osmXml.c_str()).status != pugi::status_ok)
|
||||
MYTHROW(editor::InvalidXML, ("Not valid XML:", osmXml));
|
||||
|
||||
vector<XMLFeature> features;
|
||||
for (auto const n : doc.child("osm").children())
|
||||
{
|
||||
string const name(n.name());
|
||||
// TODO(AlexZ): Add relation support.
|
||||
if (name == kNodeType || name == kWayType)
|
||||
features.push_back(XMLFeature(n)); // TODO(AlexZ): Use emplace_back when pugi supports it.
|
||||
}
|
||||
return features;
|
||||
}
|
||||
|
||||
XMLFeature::Type XMLFeature::GetType() const
|
||||
{
|
||||
return strcmp(GetRootNode().name(), "node") == 0 ? Type::Node : Type::Way;
|
||||
}
|
||||
|
||||
string XMLFeature::GetTypeString() const
|
||||
{
|
||||
return GetRootNode().name();
|
||||
}
|
||||
|
||||
bool XMLFeature::IsArea() const
|
||||
{
|
||||
if (strcmp(GetRootNode().name(), kWayType) != 0)
|
||||
|
@ -154,12 +160,16 @@ ms::LatLon XMLFeature::GetCenter() const
|
|||
return PointFromLatLon(GetRootNode());
|
||||
}
|
||||
|
||||
void XMLFeature::SetCenter(ms::LatLon const & ll)
|
||||
{
|
||||
ASSERT_EQUAL(GetRootNode().name(), string(kNodeType), ());
|
||||
SetAttribute("lat", strings::to_string_dac(ll.lat, kLatLonTolerance));
|
||||
SetAttribute("lon", strings::to_string_dac(ll.lon, kLatLonTolerance));
|
||||
}
|
||||
|
||||
void XMLFeature::SetCenter(m2::PointD const & mercatorCenter)
|
||||
{
|
||||
SetAttribute("lat", strings::to_string_dac(MercatorBounds::YToLat(mercatorCenter.y),
|
||||
kLatLonTolerance));
|
||||
SetAttribute("lon", strings::to_string_dac(MercatorBounds::XToLon(mercatorCenter.x),
|
||||
kLatLonTolerance));
|
||||
SetCenter(MercatorBounds::ToLatLon(mercatorCenter));
|
||||
}
|
||||
|
||||
string XMLFeature::GetName(string const & lang) const
|
||||
|
|
|
@ -9,16 +9,17 @@
|
|||
|
||||
#include "std/ctime.hpp"
|
||||
#include "std/iostream.hpp"
|
||||
#include "std/vector.hpp"
|
||||
|
||||
#include "3party/pugixml/src/pugixml.hpp"
|
||||
|
||||
namespace editor
|
||||
{
|
||||
DECLARE_EXCEPTION(XMLFeatureError, RootException);
|
||||
DECLARE_EXCEPTION(XMLFeatureNoNodeError, XMLFeatureError);
|
||||
DECLARE_EXCEPTION(XMLFeatureNoLatLonError, XMLFeatureError);
|
||||
DECLARE_EXCEPTION(XMLFeatureNoTimestampError, XMLFeatureError);
|
||||
DECLARE_EXCEPTION(XMLFeatureNoHeaderError, XMLFeatureError);
|
||||
DECLARE_EXCEPTION(InvalidXML, XMLFeatureError);
|
||||
DECLARE_EXCEPTION(NoLatLon, XMLFeatureError);
|
||||
DECLARE_EXCEPTION(NoTimestamp, XMLFeatureError);
|
||||
DECLARE_EXCEPTION(NoHeader, XMLFeatureError);
|
||||
|
||||
class XMLFeature
|
||||
{
|
||||
|
@ -40,6 +41,8 @@ public:
|
|||
XMLFeature(pugi::xml_node const & xml);
|
||||
XMLFeature(XMLFeature const & feature) : XMLFeature(feature.m_document) {}
|
||||
bool operator==(XMLFeature const & other) const;
|
||||
/// @returns nodes and ways from osmXml. Vector can be empty.
|
||||
static vector<XMLFeature> FromOSM(string const & osmXml);
|
||||
|
||||
void Save(ostream & ost) const;
|
||||
string ToOSMString() const;
|
||||
|
@ -48,11 +51,13 @@ public:
|
|||
void ApplyPatch(XMLFeature const & featureWithChanges);
|
||||
|
||||
Type GetType() const;
|
||||
string GetTypeString() const;
|
||||
|
||||
/// @returns true only if it is a way and it is closed (area).
|
||||
bool IsArea() const;
|
||||
|
||||
ms::LatLon GetCenter() const;
|
||||
void SetCenter(ms::LatLon const & ll);
|
||||
void SetCenter(m2::PointD const & mercatorCenter);
|
||||
|
||||
string GetName(string const & lang) const;
|
||||
|
|
Loading…
Add table
Reference in a new issue