From ef79e72eaf2be2aaa9d128657067a95e2389c572 Mon Sep 17 00:00:00 2001 From: Emillio Mariscal Date: Tue, 27 Feb 2024 21:41:29 -0300 Subject: [PATCH] Refactor, + nodes/relations support for bootstrapping --- config/validate/building.yaml | 4 +- setup/db/underpass.sql | 4 +- src/bootstrap/bootstrap.cc | 123 +++++++++++++++++++---- src/bootstrap/bootstrap.hh | 7 ++ src/osm/osmobjects.hh | 8 +- src/raw/queryraw.cc | 159 +++++++++++++++++++++--------- src/raw/queryraw.hh | 2 + src/validate/defaultvalidation.cc | 21 ++++ src/validate/defaultvalidation.hh | 4 + src/validate/semantic.cc | 81 +++++++++++++-- src/validate/semantic.hh | 1 + src/validate/validate.hh | 8 ++ 12 files changed, 343 insertions(+), 79 deletions(-) diff --git a/config/validate/building.yaml b/config/validate/building.yaml index 98b2e609..dce36bc6 100644 --- a/config/validate/building.yaml +++ b/config/validate/building.yaml @@ -7,8 +7,8 @@ config: - badgeom_maxangle: - 91 - badvalue: - - yes + - no - incomplete: - - yes + - no diff --git a/setup/db/underpass.sql b/setup/db/underpass.sql index cffcb0c6..0542e21d 100644 --- a/setup/db/underpass.sql +++ b/setup/db/underpass.sql @@ -99,7 +99,7 @@ CREATE TABLE IF NOT EXISTS public.relations ( changeset int8, geom public.geometry(Geometry,4326), tags JSONB, - members int8[], + refs int8[], timestamp timestamp with time zone, version int, "user" text, @@ -111,7 +111,7 @@ CREATE TABLE IF NOT EXISTS public.way_refs ( node_id int8 ); -CREATE TABLE IF NOT EXISTS public.rel_members ( +CREATE TABLE IF NOT EXISTS public.rel_refs ( rel_id int8, way_id int8 ); diff --git a/src/bootstrap/bootstrap.cc b/src/bootstrap/bootstrap.cc index 5749309b..2f81f22d 100644 --- a/src/bootstrap/bootstrap.cc +++ b/src/bootstrap/bootstrap.cc @@ -93,7 +93,7 @@ Bootstrap::start(const underpassconfig::UnderpassConfig &config) { processWays(); processNodes(); - // processRels(); + processRelations(); } @@ -106,22 +106,20 @@ Bootstrap::processWays() { }; for (auto table_it = tables.begin(); table_it != tables.end(); ++table_it) { - std::cout << std::endl << "Counting geometries ... " << std::endl; + std::cout << std::endl << "Processing ways ... "; long int total = queryraw->getCount(*table_it); long int count = 0; int num_chunks = total / page_size; - std::cout << "Total: " << total << std::endl; - std::cout << "Threads: " << concurrency << std::endl; - std::cout << "Page size: " << page_size << std::endl; long lastid = 0; int concurrentTasks = concurrency; int taskIndex = 0; + int percentage = 0; - for (int chunkIndex = 1; chunkIndex <= (num_chunks/concurrentTasks); chunkIndex++) { + for (int chunkIndex = 0; chunkIndex <= (num_chunks/concurrentTasks); chunkIndex++) { - int percentage = (count * 100) / total; + percentage = (count * 100) / total; auto ways = std::make_shared>(); if (!norefs) { @@ -152,6 +150,8 @@ Bootstrap::processWays() { count += it->processed; } } + percentage = (count * 100) / total; + std::cout << "\r" << "Processing " << *table_it << ": " << count << "/" << total << " (" << percentage << "%)"; } std::cout << std::endl; @@ -160,22 +160,20 @@ Bootstrap::processWays() { void Bootstrap::processNodes() { - std::cout << std::endl << "Counting nodes ... " << std::endl; + std::cout << "Processing nodes ... "; long int total = queryraw->getCount("nodes"); long int count = 0; int num_chunks = total / page_size; - std::cout << "Total: " << total << std::endl; - std::cout << "Threads: " << concurrency << std::endl; - std::cout << "Page size: " << page_size << std::endl; long lastid = 0; int concurrentTasks = concurrency; int taskIndex = 0; + int percentage = 0; - for (int chunkIndex = 1; chunkIndex <= (num_chunks/concurrentTasks); chunkIndex++) { + for (int chunkIndex = 0; chunkIndex <= (num_chunks/concurrentTasks); chunkIndex++) { - int percentage = (count * 100) / total; + percentage = (count * 100) / total; auto nodes = std::make_shared>(); nodes = queryraw->getNodesFromDB(lastid, concurrency * page_size); @@ -190,7 +188,6 @@ Bootstrap::processNodes() { std::ref(nodes), }; std::cout << "\r" << "Processing nodes: " << count << "/" << total << " (" << percentage << "%)"; - boost::asio::post(pool, boost::bind(&Bootstrap::threadBootstrapNodeTask, this, nodeTask)); } @@ -202,6 +199,57 @@ Bootstrap::processNodes() { count += it->processed; } } + percentage = (count * 100) / total; + std::cout << "\r" << "Processing nodes: " << count << "/" << total << " (" << percentage << "%)"; + std::cout << std::endl; + +} + +void +Bootstrap::processRelations() { + + std::cout << "Processing relations ... "; + long int total = queryraw->getCount("relations"); + long int count = 0; + int num_chunks = total / page_size; + + long lastid = 0; + + int concurrentTasks = concurrency; + int taskIndex = 0; + int percentage = 0; + + for (int chunkIndex = 0; chunkIndex <= (num_chunks/concurrentTasks); chunkIndex++) { + + percentage = (count * 100) / total; + + auto relations = std::make_shared>(); + relations = queryraw->getRelationsFromDB(lastid, concurrency * page_size); + + auto tasks = std::make_shared>(concurrentTasks); + boost::asio::thread_pool pool(concurrentTasks); + for (int taskIndex = 0; taskIndex < concurrentTasks; taskIndex++) { + auto taskRelations = std::make_shared>(); + RelationTask relationTask { + taskIndex, + std::ref(tasks), + std::ref(relations), + }; + std::cout << "\r" << "Processing relations: " << count << "/" << total << " (" << percentage << "%)"; + boost::asio::post(pool, boost::bind(&Bootstrap::threadBootstrapRelationTask, this, relationTask)); + } + + pool.join(); + + db->query(allTasksQueries(tasks)); + lastid = relations->back().id; + for (auto it = tasks->begin(); it != tasks->end(); ++it) { + count += it->processed; + } + } + percentage = (count * 100) / total; + std::cout << "\r" << "Processing relations: " << count << "/" << total << " (" << percentage << "%)"; + std::cout << std::endl; } @@ -223,9 +271,9 @@ Bootstrap::threadBootstrapWayTask(WayTask wayTask) auto wayval = std::make_shared>>(); // Proccesing ways - for (int i = 0; i < page_size; ++i) { - if (i * taskIndex < ways->size()) { - auto way = ways->at(i * (taskIndex + 1)); + for (int i = taskIndex * page_size; i < (taskIndex + 1) * page_size; ++i) { + if (i < ways->size()) { + auto way = ways->at(i); wayval->push_back(validator->checkWay(way, "building")); // Fill the way_refs table if (!norefs) { @@ -261,9 +309,9 @@ Bootstrap::threadBootstrapNodeTask(NodeTask nodeTask) // Proccesing nodes std::vector node_tests = {"building", "natural", "place", "waterway"}; - for (int i = 0; i < page_size; ++i) { - if (i * taskIndex < nodes->size()) { - auto node = nodes->at(i * (taskIndex + 1)); + for (int i = taskIndex * page_size; i < (taskIndex + 1) * page_size; ++i) { + if (i < nodes->size()) { + auto node = nodes->at(i); for (auto test_it = std::begin(node_tests); test_it != std::end(node_tests); ++test_it) { if (node.containsKey(*test_it)) { nodeval->push_back(validator->checkNode(node, *test_it)); @@ -279,4 +327,39 @@ Bootstrap::threadBootstrapNodeTask(NodeTask nodeTask) } +// This thread get started for every page of relation +void +Bootstrap::threadBootstrapRelationTask(RelationTask relationTask) +{ +#ifdef TIMING_DEBUG + boost::timer::auto_cpu_timer timer("bootstrap::threadBootstrapRelationTask(relationTask): took %w seconds\n"); +#endif + auto taskIndex = relationTask.taskIndex; + auto tasks = relationTask.tasks; + auto relations = relationTask.relations; + + BootstrapTask task; + int processed = 0; + + auto relationval = std::make_shared>>(); + + // Proccesing relations + for (int i = taskIndex * page_size; i < (taskIndex + 1) * page_size; ++i) { + if (i < relations->size()) { + auto relation = relations->at(i); + // relationval->push_back(validator->checkRelation(way, "building")); + // Fill the rel_members table + // for (auto ref = relation.refs.begin(); ref != relation.refs.end(); ++ref) { + // task.query += "INSERT INTO rel_refs (rel_id, way_id) VALUES (" + std::to_string(rel.id) + "," + std::to_string(*ref) + "); "; + // } + ++processed; + } + } + // queryvalidate->relations(relval, task.query); + task.processed = processed; + const std::lock_guard lock(tasks_change_mutex); + (*tasks)[taskIndex] = task; + +} + } \ No newline at end of file diff --git a/src/bootstrap/bootstrap.hh b/src/bootstrap/bootstrap.hh index 22b78d85..4142e400 100644 --- a/src/bootstrap/bootstrap.hh +++ b/src/bootstrap/bootstrap.hh @@ -49,6 +49,11 @@ struct NodeTask { std::shared_ptr> nodes; }; +struct RelationTask { + int taskIndex; + std::shared_ptr> tasks; + std::shared_ptr> relations; +}; class Bootstrap { public: @@ -61,10 +66,12 @@ class Bootstrap { void start(const underpassconfig::UnderpassConfig &config); void processWays(); void processNodes(); + void processRelations(); // This thread get started for every page of way void threadBootstrapWayTask(WayTask wayTask); void threadBootstrapNodeTask(NodeTask nodeTask); + void threadBootstrapRelationTask(RelationTask relationTask); std::string allTasksQueries(std::shared_ptr> tasks); std::shared_ptr validator; diff --git a/src/osm/osmobjects.hh b/src/osm/osmobjects.hh index e014cb80..b090c3e6 100644 --- a/src/osm/osmobjects.hh +++ b/src/osm/osmobjects.hh @@ -42,6 +42,7 @@ typedef boost::geometry::model::d2::point_xy point_t; typedef boost::geometry::model::polygon polygon_t; typedef boost::geometry::model::multi_polygon multipolygon_t; typedef boost::geometry::model::linestring linestring_t; +typedef boost::geometry::model::multi_linestring multilinestring_t; typedef boost::geometry::model::segment segment_t; typedef boost::geometry::model::point> sphere_t; @@ -174,9 +175,6 @@ class OsmWay : public OsmObject { /// Return the number of nodes in this way int numPoints(void) { return boost::geometry::num_points(linestring); }; - /// Add a point to the way's geometric data storage - // void makeLinestring(point_t point); - /// Calculate the length of the linestring in Kilometers double getLength(void) { @@ -220,6 +218,10 @@ class OsmRelation : public OsmObject { public: OsmRelation(void) { type = relation; }; + multilinestring_t multilinestring; ///< Store the members as a linestring + multipolygon_t multipolygon; ///< Store the members as a multipolygon + point_t center; ///< Store the centroid of the relation + /// Add a member to this relation void addMember(long ref, osmtype_t _type, const std::string role) { members.push_back({ref, _type, role}); }; diff --git a/src/raw/queryraw.cc b/src/raw/queryraw.cc index b3be52e4..9decd2ec 100644 --- a/src/raw/queryraw.cc +++ b/src/raw/queryraw.cc @@ -377,20 +377,42 @@ QueryRaw::getNodeCacheFromWays(std::shared_ptr> ways, std::m } } -std::map parseTagsString(std::string input) { - std::map tags; +std::map parseJSONObjectStr(std::string input) { + std::map obj; boost::property_tree::ptree pt; try { std::istringstream jsonStream(input); boost::property_tree::read_json(jsonStream, pt); } catch (const boost::property_tree::json_parser::json_parser_error& e) { std::cerr << "Error parsing JSON: " << e.what() << std::endl; - return tags; + return obj; } for (const auto& pair : pt) { - tags[pair.first] = pair.second.get_value(); + obj[pair.first] = pair.second.get_value(); } - return tags; + return obj; +} + +std::vector> parseJSONArrayStr(std::string input) { + std::vector> arr; + boost::property_tree::ptree pt; + try { + std::istringstream jsonStream(input); + boost::property_tree::read_json(jsonStream, pt); + } catch (const boost::property_tree::json_parser::json_parser_error& e) { + std::cerr << "Error parsing JSON: " << e.what() << std::endl; + return arr; + } + + for (const auto& item : pt) { + std::map obj; + for (const auto& pair : item.second) { + obj[pair.first] = pair.second.get_value(); + } + arr.push_back(obj); + } + + return arr; } std::list> @@ -417,7 +439,7 @@ QueryRaw::getWaysByNodesRefs(std::string &nodeIds) const way->version = (*way_it)[2].as(); auto tags = (*way_it)[3]; if (!tags.is_null()) { - auto tags = parseTagsString((*way_it)[3].as()); + auto tags = parseJSONObjectStr((*way_it)[3].as()); for (auto const& [key, val] : tags) { way->addTag(key, val); @@ -442,6 +464,43 @@ int QueryRaw::getCount(const std::string &tableName) { return result[0][0].as(); } +std::shared_ptr> +QueryRaw::getNodesFromDB(long lastid, int pageSize) { + std::string nodesQuery = "SELECT osm_id, ST_AsText(geom, 4326)"; + + if (lastid > 0) { + nodesQuery += ", version, tags FROM nodes where osm_id < " + std::to_string(lastid) + " order by osm_id desc limit " + std::to_string(pageSize) + ";"; + } else { + nodesQuery += ", version, tags FROM nodes order by osm_id desc limit " + std::to_string(pageSize) + ";"; + } + + auto nodes_result = dbconn->query(nodesQuery); + // Fill vector of OsmNode objects + auto nodes = std::make_shared>(); + for (auto node_it = nodes_result.begin(); node_it != nodes_result.end(); ++node_it) { + OsmNode node; + node.id = (*node_it)[0].as(); + + point_t point; + std::string point_str = (*node_it)[1].as(); + boost::geometry::read_wkt(point_str, point); + node.setPoint(boost::geometry::get<0>(point), boost::geometry::get<1>(point)); + node.version = (*node_it)[2].as(); + auto tags = (*node_it)[3]; + if (!tags.is_null()) { + auto tags = parseJSONObjectStr((*node_it)[3].as()); + for (auto const& [key, val] : tags) + { + node.addTag(key, val); + } + } + nodes->push_back(node); + } + + return nodes; +} + + std::shared_ptr> QueryRaw::getWaysFromDB(long lastid, int pageSize, const std::string &tableName) { std::string waysQuery; @@ -475,7 +534,7 @@ QueryRaw::getWaysFromDB(long lastid, int pageSize, const std::string &tableName) way.version = (*way_it)[3].as(); auto tags = (*way_it)[4]; if (!tags.is_null()) { - auto tags = parseTagsString((*way_it)[4].as()); + auto tags = parseJSONObjectStr((*way_it)[4].as()); for (auto const& [key, val] : tags) { way.addTag(key, val); @@ -488,42 +547,6 @@ QueryRaw::getWaysFromDB(long lastid, int pageSize, const std::string &tableName) return ways; } -std::shared_ptr> -QueryRaw::getNodesFromDB(long lastid, int pageSize) { - std::string nodesQuery = "SELECT osm_id, ST_AsText(geom, 4326)"; - - if (lastid > 0) { - nodesQuery += ", version, tags FROM nodes where osm_id < " + std::to_string(lastid) + " order by osm_id desc limit " + std::to_string(pageSize) + ";"; - } else { - nodesQuery += ", version, tags FROM nodes order by osm_id desc limit " + std::to_string(pageSize) + ";"; - } - - auto nodes_result = dbconn->query(nodesQuery); - // Fill vector of OsmNode objects - auto nodes = std::make_shared>(); - for (auto node_it = nodes_result.begin(); node_it != nodes_result.end(); ++node_it) { - OsmNode node; - node.id = (*node_it)[0].as(); - - point_t point; - std::string point_str = (*node_it)[1].as(); - boost::geometry::read_wkt(point_str, point); - node.setPoint(boost::geometry::get<0>(point), boost::geometry::get<1>(point)); - node.version = (*node_it)[2].as(); - auto tags = (*node_it)[3]; - if (!tags.is_null()) { - auto tags = parseTagsString((*node_it)[3].as()); - for (auto const& [key, val] : tags) - { - node.addTag(key, val); - } - } - nodes->push_back(node); - } - - return nodes; -} - std::shared_ptr> QueryRaw::getWaysFromDBWithoutRefs(long lastid, int pageSize, const std::string &tableName) { std::string waysQuery; @@ -553,7 +576,7 @@ QueryRaw::getWaysFromDBWithoutRefs(long lastid, int pageSize, const std::string } auto tags = (*way_it)[2]; if (!tags.is_null()) { - auto tags = parseTagsString((*way_it)[2].as()); + auto tags = parseJSONObjectStr((*way_it)[2].as()); for (auto const& [key, val] : tags) { way.addTag(key, val); @@ -566,6 +589,54 @@ QueryRaw::getWaysFromDBWithoutRefs(long lastid, int pageSize, const std::string return ways; } +std::shared_ptr> +QueryRaw::getRelationsFromDB(long lastid, int pageSize) { + std::string relationsQuery = "SELECT osm_id, refs, ST_AsText(geom, 4326)"; + if (lastid > 0) { + relationsQuery += ", version, tags FROM relations where osm_id < " + std::to_string(lastid) + " order by osm_id desc limit " + std::to_string(pageSize) + ";"; + } else { + relationsQuery += ", version, tags FROM relations order by osm_id desc limit " + std::to_string(pageSize) + ";"; + } + + auto relations_result = dbconn->query(relationsQuery); + // Fill vector of OsmRelation objects + auto relations = std::make_shared>(); + for (auto rel_it = relations_result.begin(); rel_it != relations_result.end(); ++rel_it) { + OsmRelation relation; + relation.id = (*rel_it)[0].as(); + auto refs = (*rel_it)[1]; + if (!refs.is_null()) { + auto refs = parseJSONArrayStr((*rel_it)[1].as()); + for (auto ref_it = refs.begin(); ref_it != refs.end(); ++ref_it) { + if (ref_it->at("type") == "w" && (ref_it->at("role") == "inner" || ref_it->at("role") == "outer")) { + relation.addMember( + std::stoi(ref_it->at("ref")), + osmobjects::osmtype_t::way, + ref_it->at("role") + ); + } + } + std::string geometry = (*rel_it)[2].as(); + if (geometry.substr(0, 12) == "MULTIPOLYGON") { + boost::geometry::read_wkt(geometry, relation.multipolygon); + } + relation.version = (*rel_it)[3].as(); + } + auto tags = (*rel_it)[4]; + if (!tags.is_null()) { + auto tags = parseJSONObjectStr((*rel_it)[4].as()); + for (auto const& [key, val] : tags) + { + relation.addTag(key, val); + } + } + relations->push_back(relation); + } + + return relations; +} + + } // namespace queryraw // local Variables: diff --git a/src/raw/queryraw.hh b/src/raw/queryraw.hh index c6e2317f..8cabf4cf 100644 --- a/src/raw/queryraw.hh +++ b/src/raw/queryraw.hh @@ -84,6 +84,8 @@ class QueryRaw { std::shared_ptr> getWaysFromDBWithoutRefs(long lastid, int pageSize, const std::string &tableName); // Get nodes by page std::shared_ptr> getNodesFromDB(long lastid, int pageSize); + // Get relations by page + std::shared_ptr> getRelationsFromDB(long lastid, int pageSize); }; diff --git a/src/validate/defaultvalidation.cc b/src/validate/defaultvalidation.cc index 5a95502c..b4520ad4 100644 --- a/src/validate/defaultvalidation.cc +++ b/src/validate/defaultvalidation.cc @@ -77,6 +77,27 @@ DefaultValidation::checkWay(const osmobjects::OsmWay &way, const std::string &ty return status; } +// This checks a relation. A relation should always have some tags. +std::shared_ptr +DefaultValidation::checkRelation(const osmobjects::OsmRelation &relation, const std::string &type) +{ + auto status = std::make_shared(relation); + status->timestamp = boost::posix_time::microsec_clock::universal_time(); + status->uid = relation.uid; + if (yamls.size() == 0) { + log_error("No config files!"); + return status; + } + yaml::Yaml tests = yamls[type]; + semantic::Semantic::checkRelation(relation, type, tests, status); + // geospatial::Geospatial::checkRelation(relation, type, tests, status); + // if (relation.linestring.size() > 2) { + // boost::geometry::centroid(way.linestring, status->center); + // } + status->source = type; + return status; +} + }; // namespace defaultvalidation #endif // EOF __DEFAULTVALIDATION_H__ diff --git a/src/validate/defaultvalidation.hh b/src/validate/defaultvalidation.hh index d3bf20f2..46958dea 100644 --- a/src/validate/defaultvalidation.hh +++ b/src/validate/defaultvalidation.hh @@ -55,6 +55,10 @@ public: /// is a building std::shared_ptr checkWay(const osmobjects::OsmWay &way, const std::string &type); + + /// This checks a relation. A relation should always have some tags. + std::shared_ptr checkRelation(const osmobjects::OsmRelation &relation, const std::string &type); + // Factory method static std::shared_ptr create(void) { return std::make_shared(); diff --git a/src/validate/semantic.cc b/src/validate/semantic.cc index ac5a955b..91f8581c 100644 --- a/src/validate/semantic.cc +++ b/src/validate/semantic.cc @@ -116,6 +116,11 @@ bool Semantic::isRequiredTag(const std::string &key, yaml::Node required_tags) { std::shared_ptr Semantic::checkNode(const osmobjects::OsmNode &node, const std::string &type, yaml::Yaml &tests, std::shared_ptr &status) { + + auto config = tests.get("config"); + bool check_badvalue = config.get_value("badvalue") == "yes"; + bool check_incomplete = config.get_value("incomplete") == "yes"; + if (node.tags.size() == 0) { status->status.insert(notags); return status; @@ -137,18 +142,24 @@ Semantic::checkNode(const osmobjects::OsmNode &node, const std::string &type, ya if (node.tags.count(type)) { for (auto vit = std::begin(node.tags); vit != std::end(node.tags); ++vit) { - if (!isValidTag(vit->first, vit->second, tags)) { - status->status.insert(badvalue); - status->values.insert(vit->first + "=" + vit->second); + if (check_badvalue) { + if (!isValidTag(vit->first, vit->second, tags)) { + status->status.insert(badvalue); + status->values.insert(vit->first + "=" + vit->second); + } } - if (isRequiredTag(vit->first, required_tags)) { - tagexists++; + if (check_incomplete) { + if (isRequiredTag(vit->first, required_tags)) { + tagexists++; + } } checkTag(vit->first, vit->second, status); } - if (tagexists != required_tags.children.size()) { - status->status.insert(incomplete); + if (check_incomplete) { + if (tagexists != required_tags.children.size()) { + status->status.insert(incomplete); + } } } return status; @@ -177,7 +188,7 @@ Semantic::checkWay(const osmobjects::OsmWay &way, const std::string &type, yaml: // List of valid tags to be validated yaml::Node tags; if (check_badvalue) { - tags = tests.get("tags"); + tags = tests.get("tags"); } if (check_badvalue && way.tags.size() == 0) { @@ -209,6 +220,60 @@ Semantic::checkWay(const osmobjects::OsmWay &way, const std::string &type, yaml: return status; } +// This checks a relation. +std::shared_ptr +Semantic::checkRelation(const osmobjects::OsmRelation &relation, const std::string &type, yaml::Yaml &tests, std::shared_ptr &status) +{ + if (relation.action == osmobjects::remove) { + return status; + } + + // These values are in the config section of the YAML file + auto config = tests.get("config"); + bool check_badvalue = config.get_value("badvalue") == "yes"; + bool check_incomplete = config.get_value("incomplete") == "yes"; + + // List of required tags to be validated + yaml::Node required_tags; + if (check_incomplete) { + required_tags = tests.get("required_tags"); + } + + // List of valid tags to be validated + yaml::Node tags; + if (check_badvalue) { + tags = tests.get("tags"); + } + + if (check_badvalue && relation.tags.size() == 0) { + status->status.insert(notags); + return status; + } + + int tagexists = 0; + if (relation.tags.count(type)) { + for (auto vit = std::begin(relation.tags); vit != std::end(relation.tags); ++vit) { + if (check_badvalue) { + if (tags.children.size() > 0 && !isValidTag(vit->first, vit->second, tags)) { + status->status.insert(badvalue); + status->values.insert(vit->first + "=" + vit->second); + } + checkTag(vit->first, vit->second, status); + } + if (check_incomplete) { + if (isRequiredTag(vit->first, required_tags)) { + tagexists++; + } + } + } + + if (check_incomplete && tagexists != required_tags.children.size()) { + status->status.insert(incomplete); + } + } + return status; +} + }; // namespace semantic #endif // EOF __SEMANTIC_H__ diff --git a/src/validate/semantic.hh b/src/validate/semantic.hh index 14f93e11..7dfe3a85 100644 --- a/src/validate/semantic.hh +++ b/src/validate/semantic.hh @@ -57,6 +57,7 @@ public: ~Semantic(void) { }; static std::shared_ptr checkNode(const osmobjects::OsmNode &node, const std::string &type, yaml::Yaml &tests, std::shared_ptr &status); static std::shared_ptr checkWay(const osmobjects::OsmWay &way, const std::string &type, yaml::Yaml &tests, std::shared_ptr &status); + static std::shared_ptr checkRelation(const osmobjects::OsmRelation &relation, const std::string &type, yaml::Yaml &tests, std::shared_ptr &status); private: static bool isValidTag(const std::string &key, const std::string &value, yaml::Node tags); static bool isRequiredTag(const std::string &key, yaml::Node required_tags); diff --git a/src/validate/validate.hh b/src/validate/validate.hh index e8d51e0e..d3ed80f2 100644 --- a/src/validate/validate.hh +++ b/src/validate/validate.hh @@ -118,6 +118,14 @@ class ValidateStatus { version = way.version; timestamp = way.timestamp; } + ValidateStatus(const osmobjects::OsmRelation &relation) { + osm_id = relation.id; + uid = relation.uid; + changeset = relation.changeset; + objtype = osmobjects::relation; + version = relation.version; + timestamp = relation.timestamp; + } /// Does this change have a particular status value bool hasStatus(const valerror_t &val) const { auto match = std::find(status.begin(), status.end(), val);