diff --git a/hoot-core/src/main/cpp/hoot/core/io/OsmApiChangeset.cpp b/hoot-core/src/main/cpp/hoot/core/io/OsmApiChangeset.cpp
index e425d27..2c9087d 100644
--- a/hoot-core/src/main/cpp/hoot/core/io/OsmApiChangeset.cpp
+++ b/hoot-core/src/main/cpp/hoot/core/io/OsmApiChangeset.cpp
@@ -32,6 +32,7 @@
#include <hoot/core/util/ConfigOptions.h>
#include <hoot/core/util/FileUtils.h>
#include <hoot/core/util/Log.h>
+#include <hoot/core/util/StringUtils.h>
// Standard
#include <algorithm>
@@ -50,13 +51,27 @@ XmlChangeset::XmlChangeset()
_ways(ChangesetType::TypeMax),
_relations(ChangesetType::TypeMax),
_maxPushSize(ConfigOptions().getChangesetApidbSizeMax()),
+ _maxChangesetSize(ConfigOptions().getChangesetMaxSize()),
_sentCount(0),
_processedCount(0),
_failedCount(0)
{
}
-XmlChangeset::XmlChangeset(const QList<QString> &changesets)
+XmlChangeset::XmlChangeset(const QString& changeset)
+ : _nodes(ChangesetType::TypeMax),
+ _ways(ChangesetType::TypeMax),
+ _relations(ChangesetType::TypeMax),
+ _maxPushSize(ConfigOptions().getChangesetApidbSizeMax()),
+ _maxChangesetSize(ConfigOptions().getChangesetMaxSize()),
+ _sentCount(0),
+ _processedCount(0),
+ _failedCount(0)
+{
+ loadChangeset(changeset);
+}
+
+XmlChangeset::XmlChangeset(const QList<QString>& changesets)
: _nodes(ChangesetType::TypeMax),
_ways(ChangesetType::TypeMax),
_relations(ChangesetType::TypeMax),
@@ -69,10 +84,16 @@ XmlChangeset::XmlChangeset(const QList<QString> &changesets)
loadChangeset(*it);
}
-void XmlChangeset::loadChangeset(const QString &changeset)
+void XmlChangeset::loadChangeset(const QString& changeset)
{
- if (QFile::exists(changeset))
- loadChangesetFile(changeset);
+ QFileInfo fi(changeset);
+ if (fi.exists())
+ {
+ if (fi.isDir())
+ loadChangesetDirectory(changeset);
+ else
+ loadChangesetFile(changeset);
+ }
else
loadChangesetXml(changeset);
}
@@ -95,7 +116,7 @@ void XmlChangeset::loadChangesetFile(const QString& path)
loadOsmAsChangeset(reader);
}
-void XmlChangeset::loadChangesetXml(const QString &changesetXml)
+void XmlChangeset::loadChangesetXml(const QString& changesetXml)
{
// Load the XML directly into the reader
QXmlStreamReader reader(changesetXml);
@@ -103,7 +124,49 @@ void XmlChangeset::loadChangesetXml(const QString &changesetXml)
loadChangeset(reader);
}
-void XmlChangeset::loadChangeset(QXmlStreamReader &reader)
+void XmlChangeset::loadChangesetDirectory(const QString& changesetDirectory)
+{
+ // Iterate all of the files in the directory, loading them one by one
+ QDir dir(changesetDirectory);
+ dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
+ dir.setSorting(QDir::Name | QDir::IgnoreCase);
+ QStringList filters;
+ filters << "*.osc";
+ dir.setNameFilters(filters);
+
+ QFileInfoList files = dir.entryInfoList();
+ for (int i = 0; i < files.size(); ++i)
+ {
+ QFileInfo fileInfo = files.at(i);
+ QString filepath = fileInfo.absoluteFilePath();
+ // Check if this file is in the request/response debug output format (including the error file)
+ if (filepath.contains("-Request--") && filepath.endsWith("000.osc", Qt::CaseInsensitive))
+ {
+ // Only load the requests that were successful, i.e. response 200
+ QString response = filepath;
+ response.replace("-Request--", "-Response-").replace("000.osc", "200.osc");
+ if (QFile::exists(response))
+ loadChangesetFile(filepath);
+ }
+ else if (filepath.contains("-Response-") && filepath.endsWith("200.osc", Qt::CaseInsensitive))
+ {
+ // Read in the update for new IDs
+ updateChangeset(FileUtils::readFully(filepath));
+ }
+ else if (filepath.endsWith("-error.osc", Qt::CaseInsensitive))
+ {
+ // The changeset error file
+ loadChangesetFile(filepath);
+ }
+ else
+ {
+ // Output the filename to the log
+ LOG_DEBUG("Skipping file in folder: " << filepath);
+ }
+ }
+}
+
+void XmlChangeset::loadChangeset(QXmlStreamReader& reader)
{
// Make sure that the XML provided starts with the <diffResult> tag
QXmlStreamReader::TokenType type = reader.readNext();
@@ -314,7 +377,18 @@ void XmlChangeset::fixMalformedInput()
writeErrorFile();
}
-void XmlChangeset::updateChangeset(const QString &changes)
+QString XmlChangeset::getString(ChangesetType type)
+{
+ switch (type)
+ {
+ case ChangesetType::TypeCreate: return "<create>";
+ case ChangesetType::TypeModify: return "<modify>";
+ case ChangesetType::TypeDelete: return "<delete>";
+ default: return "";
+ }
+}
+
+void XmlChangeset::updateChangeset(const QString& changes)
{
/* <diffResult generator="OpenStreetMap Server" version="0.6">
* <node|way|relation old_id="#" new_id="#" new_version="#"/>
@@ -361,41 +435,10 @@ void XmlChangeset::updateChangeset(const QString &changes)
updateElement(_ways, old_id, new_id, version);
else if (name == "relation")
updateElement(_relations, old_id, new_id, version);
- }
- }
-}
-
-void XmlChangeset::updateChangeset(const ChangesetInfoPtr& changeset_info)
-{
- // Iterate the three changeset type arrays looking for elements to mark
- for (int current_type = ChangesetType::TypeCreate; current_type != ChangesetType::TypeMax; ++current_type)
- {
- // Set the relation's status to failed
- for (ChangesetInfo::iterator it = changeset_info->begin(ElementType::Relation, (ChangesetType)current_type);
- it != changeset_info->end(ElementType::Relation, (ChangesetType)current_type); ++it)
- {
- // Finalize the relation
- _allRelations[*it]->setStatus(ChangesetElement::ElementStatus::Finalized);
- // Update the processed count
- _processedCount++;
- }
- // Set the way's status to failed
- for (ChangesetInfo::iterator it = changeset_info->begin(ElementType::Way, (ChangesetType)current_type);
- it != changeset_info->end(ElementType::Way, (ChangesetType)current_type); ++it)
- {
- // Finalize the way
- _allWays[*it]->setStatus(ChangesetElement::ElementStatus::Finalized);
- // Update the processed count
- _processedCount++;
- }
- // Set the node's status to failed
- for (ChangesetInfo::iterator it = changeset_info->begin(ElementType::Node, (ChangesetType)current_type);
- it != changeset_info->end(ElementType::Node, (ChangesetType)current_type); ++it)
- {
- // Finalize the node
- _allNodes[*it]->setStatus(ChangesetElement::ElementStatus::Finalized);
- // Update the processed count
- _processedCount++;
+ if (old_id == 0)
+ {
+ LOG_ERROR("Element cannot be updated. No ID given.");
+ }
}
}
}
@@ -491,6 +534,7 @@ bool XmlChangeset::addNode(ChangesetInfoPtr& changeset, ChangesetType type, Chan
// Only add the nodes that are "sendable"
if (canSend(node))
{
+ // No need to getObjectCount() like addWay and addRelation because it is either 0 or 1
// Add create nodes if the ID map's ID is negative, modify IDs don't matter
if ((type == ChangesetType::TypeCreate && _idMap.getId(ElementType::Node, node->id()) < 0) ||
type != ChangesetType::TypeDelete)
@@ -572,8 +616,14 @@ bool XmlChangeset::addWays(ChangesetInfoPtr& changeset, ChangesetType type)
bool XmlChangeset::addWay(ChangesetInfoPtr& changeset, ChangesetType type, ChangesetWay* way)
{
+ // Check if the way is able to be sent
if (canSend(way))
{
+ // The number of elements in this way (fully "hydrated") cannot exceed the max size in a changeset
+ ElementCountSet elements(ElementType::Max);
+ size_t count = getObjectCount(way, elements);
+ if (count + changeset->size() > (size_t)_maxChangesetSize)
+ return false;
bool sendable = true;
// Only creates/modifies require pre-processing
if (type != ChangesetType::TypeDelete)
@@ -695,7 +745,8 @@ bool XmlChangeset::canMoveWay(ChangesetInfoPtr& source, ChangesetInfoPtr& /*dest
if (type == ChangesetType::TypeDelete)
return source->size() == 1;
// Get the count of nodes and way that make up this "change"
- size_t count = getObjectCount(source, way);
+ ElementCountSet elements(ElementType::Max);
+ size_t count = getObjectCount(source, way, elements);
// Compare that count to the size left in the source changeset
return source->size() != count;
}
@@ -716,8 +767,14 @@ bool XmlChangeset::addRelations(ChangesetInfoPtr& changeset, ChangesetType type)
bool XmlChangeset::addRelation(ChangesetInfoPtr& changeset, ChangesetType type, ChangesetRelation* relation)
{
+ // Check if the relation is able to be sent
if (canSend(relation))
{
+ // The number of elements in this relation (fully "hydrated") cannot exceed the max size in a changeset
+ ElementCountSet elements(ElementType::Max);
+ size_t count = getObjectCount(relation, elements);
+ if (count + changeset->size() > (size_t)_maxChangesetSize)
+ return false;
bool sendable = true;
// Deletes require no pre-processing
if (type != ChangesetType::TypeDelete)
@@ -895,43 +952,89 @@ bool XmlChangeset::canMoveRelation(ChangesetInfoPtr& source, ChangesetInfoPtr& /
if (type == ChangesetType::TypeDelete)
return source->size() == 1;
// Get the count of nodes, ways, and relations that make up this "change"
- size_t count = getObjectCount(source, relation);
+ ElementCountSet elements(ElementType::Max);
+ size_t count = getObjectCount(source, relation, elements);
// Compare that count to the size left in the source changeset
return source->size() != count;
}
-size_t XmlChangeset::getObjectCount(ChangesetInfoPtr& /*changeset*/, ChangesetNode* node)
+size_t XmlChangeset::getObjectCount(ChangesetNode* node, ElementCountSet& elements)
{
+ ChangesetInfoPtr empty;
+ return getObjectCount(empty, node, elements);
+}
+
+size_t XmlChangeset::getObjectCount(ChangesetWay* way, ElementCountSet& elements)
+{
+ ChangesetInfoPtr empty;
+ return getObjectCount(empty, way, elements);
+}
+
+size_t XmlChangeset::getObjectCount(ChangesetRelation* relation, ElementCountSet& elements)
+{
+ ChangesetInfoPtr empty;
+ return getObjectCount(empty, relation, elements);
+}
+
+size_t XmlChangeset::getObjectCount(ChangesetInfoPtr& /*changeset*/, ChangesetNode* node, ElementCountSet& elements)
+{
+ // Cannot count NULL nodes
if (node == NULL)
return 0;
+ // Do not recount nodes already counted
+ if (elements[ElementType::Node].find(node->id()) != elements[ElementType::Node].end())
+ return 0;
+ // Record the node ID in the node elements set
+ elements[ElementType::Node].insert(node->id());
// Nodes count as one object
return 1;
}
-size_t XmlChangeset::getObjectCount(ChangesetInfoPtr& changeset, ChangesetWay* way)
+size_t XmlChangeset::getObjectCount(ChangesetInfoPtr& changeset, ChangesetWay* way, ElementCountSet& elements)
{
if (way == NULL)
return 0;
// Get the count of nodes and way that make up this "change"
- size_t count = 1;
+ size_t count = 0;
+ // Increment the count if it isn't already found in the element set
+ if (elements[ElementType::Way].find(way->id()) == elements[ElementType::Way].end())
+ {
+ elements[ElementType::Way].insert(way->id());
+ ++count;
+ }
+ // Iterate all of the nodes
for (int i = 0; i < way->getNodeCount(); ++i)
{
long id = way->getNode(i);
+ // Look for the node in the list of nodes
for (int current_type = ChangesetType::TypeCreate; current_type != ChangesetType::TypeMax; ++current_type)
{
- if (changeset->contains(ElementType::Node, (ChangesetType)current_type, id))
- ++count;
+ // Do not recount this node
+ if (!changeset || changeset->contains(ElementType::Node, (ChangesetType)current_type, id))
+ {
+ ChangesetNode* node = NULL;
+ if (_allNodes.find(id) != _allNodes.end())
+ node = dynamic_cast<ChangesetNode*>(_allNodes[id].get());
+ count += getObjectCount(changeset, node, elements);
+ }
}
}
return count;
}
-size_t XmlChangeset::getObjectCount(ChangesetInfoPtr& changeset, ChangesetRelation* relation)
+size_t XmlChangeset::getObjectCount(ChangesetInfoPtr& changeset, ChangesetRelation* relation, ElementCountSet& elements)
{
if (relation == NULL)
return 0;
// Get the count of nodes, ways, and relations that make up this "change"
- size_t count = 1;
+ size_t count = 0;
+ // Increment the count if the relation isn't already found in the element set
+ if (elements[ElementType::Relation].find(relation->id()) == elements[ElementType::Relation].end())
+ {
+ elements[ElementType::Relation].insert(relation->id());
+ ++count;
+ }
+ // Iterate all of the relation elements
for (int i = 0; i < relation->getMemberCount(); ++i)
{
ChangesetRelationMember& member = relation->getMember(i);
@@ -941,16 +1044,26 @@ size_t XmlChangeset::getObjectCount(ChangesetInfoPtr& changeset, ChangesetRelati
{
for (int current_type = ChangesetType::TypeCreate; current_type != ChangesetType::TypeMax; ++current_type)
{
- if (changeset->contains(ElementType::Node, (ChangesetType)current_type, id))
- count += getObjectCount(changeset, dynamic_cast<ChangesetNode*>(_allNodes[id].get()));
+ if (!changeset || changeset->contains(ElementType::Node, (ChangesetType)current_type, id))
+ {
+ ChangesetNode* node = NULL;
+ if (_allNodes.find(id) != _allNodes.end())
+ node = dynamic_cast<ChangesetNode*>(_allNodes[id].get());
+ count += getObjectCount(changeset, node, elements);
+ }
}
}
else if (member.isWay())
{
for (int current_type = ChangesetType::TypeCreate; current_type != ChangesetType::TypeMax; ++current_type)
{
- if (changeset->contains(ElementType::Way, (ChangesetType)current_type, id))
- count += getObjectCount(changeset, dynamic_cast<ChangesetWay*>(_allWays[id].get()));
+ if (!changeset || changeset->contains(ElementType::Way, (ChangesetType)current_type, id))
+ {
+ ChangesetWay* way = NULL;
+ if (_allWays.find(id) != _allWays.end())
+ way = dynamic_cast<ChangesetWay*>(_allWays[id].get());
+ count += getObjectCount(changeset, way, elements);
+ }
}
}
else if (member.isRelation())
@@ -960,8 +1073,13 @@ size_t XmlChangeset::getObjectCount(ChangesetInfoPtr& changeset, ChangesetRelati
{
for (int current_type = ChangesetType::TypeCreate; current_type != ChangesetType::TypeMax; ++current_type)
{
- if (changeset->contains(ElementType::Relation, (ChangesetType)current_type, id))
- count += getObjectCount(changeset, dynamic_cast<ChangesetRelation*>(_allRelations[id].get()));
+ if (!changeset || changeset->contains(ElementType::Relation, (ChangesetType)current_type, id))
+ {
+ ChangesetRelation* relation = NULL;
+ if (_allRelations.find(id) != _allRelations.end())
+ relation = dynamic_cast<ChangesetRelation*>(_allRelations[id].get());
+ count += getObjectCount(changeset, relation, elements);
+ }
}
}
}
@@ -1576,10 +1694,9 @@ QString XmlChangeset::getChangeset(ChangesetInfoPtr changeset, long changeset_id
category = "delete";
if (changeset->size(ElementType::Node, type) > 0 || changeset->size(ElementType::Way, type) > 0 || changeset->size(ElementType::Relation, type) > 0)
{
- ts << "\t<" << category;
+ ts << "\t<" << category << ">\n";
if (type != ChangesetType::TypeDelete)
{
- ts << ">\n";
// Nodes go first in each category
writeNodes(changeset, ts, type, changeset_id);
// Followed by ways
@@ -1589,7 +1706,6 @@ QString XmlChangeset::getChangeset(ChangesetInfoPtr changeset, long changeset_id
}
else
{
- ts << " if-unused=\"true\">\n";
// Relations first for deletes
writeRelations(changeset, ts, type, changeset_id);
// Followed by ways
@@ -1631,6 +1747,10 @@ void XmlChangeset::updateElement(ChangesetTypeMap& map, long old_id, long new_id
element->setVersion(version);
_processedCount++;
}
+ else
+ {
+ LOG_ERROR("Element cannot be updated. ID " << old_id);
+ }
}
bool XmlChangeset::fixElement(ChangesetTypeMap& map, long id, long version, QMap<QString, QString> tags)
@@ -1844,8 +1964,149 @@ bool XmlChangeset::calculateRemainingChangeset(ChangesetInfoPtr &changeset)
}
// Clear the send buffer
_sendBuffer.clear();
+ bool empty_changeset = changeset->size() == 0;
+ // Output the remaining changeset to a file to inform the user where the issues lie
+ if (!empty_changeset && !_errorPathname.isEmpty())
+ {
+ // Replace error with remaining in the error pathname
+ QString pathname = _errorPathname;
+ pathname.replace("error", "remaining");
+ // Write the file with changeset ID of zero
+ FileUtils::writeFully(pathname, this->getChangesetString(changeset, 0));
+ }
// Return true if there is anything in the changeset
- return changeset->size() > 0;
+ return !empty_changeset;
+}
+
+bool XmlChangeset::isMatch(const XmlChangeset& changeset)
+{
+ bool isEqual = true;
+ // Check counts first
+ if (_allNodes.size() != changeset._allNodes.size())
+ {
+ LOG_WARN("Node count not equal (ref: " << _allNodes.size() << ", test: " << changeset._allNodes.size() << ")");
+ isEqual = false;
+ }
+ if (_allWays.size() != changeset._allWays.size())
+ {
+ LOG_WARN("Way count not equal (ref: " << _allWays.size() << ", test: " << changeset._allWays.size() << ")");
+ isEqual = false;
+ }
+ if (_allRelations.size() != changeset._allRelations.size())
+ {
+ LOG_WARN("Relation count not equal (ref: " << _allRelations.size() << ", test: " << changeset._allRelations.size() << ")");
+ isEqual = false;
+ }
+ // Now check each type, create, modify, delete
+ for (int changeset_type = ChangesetType::TypeCreate; changeset_type != ChangesetType::TypeMax; ++changeset_type)
+ {
+ // Iterate all of the nodes of "type" in the changeset
+ QSet<long> missingNodes;
+ for (ChangesetElementMap::iterator it = _nodes[changeset_type].begin(); it != _nodes[changeset_type].end(); ++it)
+ {
+ ChangesetNode* node = dynamic_cast<ChangesetNode*>(it->second.get());
+ ChangesetElementMap::const_iterator found = changeset._nodes[changeset_type].find(node->id());
+ if (found != changeset._nodes[changeset_type].end())
+ {
+ // Compare the two nodes
+ ChangesetNode* node2 = dynamic_cast<ChangesetNode*>(found->second.get());
+ QString output;
+ if (!node->diff(*node2, output))
+ {
+ // Display the node diff
+ output.chop(1);
+ LOG_WARN("Node ID " << node->id() << "\n" << output);
+ }
+ }
+ else
+ missingNodes.insert(node->id());
+ }
+ // Output missing nodes in full
+ if (missingNodes.size() > 0)
+ {
+ QString buffer;
+ QTextStream ts(&buffer);
+ ts.setCodec("UTF-8");
+ for (QSet<long>::iterator it = missingNodes.begin(); it != missingNodes.end(); ++it)
+ ts << _nodes[changeset_type][*it]->toString(0);
+ buffer.chop(1);
+ buffer.replace("\n", "\n>");
+ LOG_WARN("Missing nodes: " << getString(static_cast<ChangesetType>(changeset_type)) <<
+ " - " << missingNodes << "\n>" << buffer);
+ isEqual = false;
+ }
+ // Iterate all of the ways of "type" in the changeset
+ QSet<long> missingWays;
+ for (ChangesetElementMap::iterator it = _ways[changeset_type].begin(); it != _ways[changeset_type].end(); ++it)
+ {
+ ChangesetWay* way = dynamic_cast<ChangesetWay*>(it->second.get());
+ ChangesetElementMap::const_iterator found = changeset._ways[changeset_type].find(way->id());
+ if (found != changeset._ways[changeset_type].end())
+ {
+ // Compare the two ways
+ ChangesetWay* way2 = dynamic_cast<ChangesetWay*>(found->second.get());
+ QString output;
+ if (!way->diff(*way2, output))
+ {
+ // Display the way diff
+ output.chop(1);
+ LOG_WARN("Way ID " << way->id() << "\n" << output);
+ }
+ }
+ else
+ missingWays.insert(way->id());
+ }
+ // Output missing ways in full
+ if (missingWays.size() > 0)
+ {
+ QString buffer;
+ QTextStream ts(&buffer);
+ ts.setCodec("UTF-8");
+ for (QSet<long>::iterator it = missingWays.begin(); it != missingWays.end(); ++it)
+ ts << _ways[changeset_type][*it]->toString(0);
+ buffer.chop(1);
+ buffer.replace("\n", "\n>");
+ LOG_WARN("Missing ways: " << getString(static_cast<ChangesetType>(changeset_type)) <<
+ " - " << missingWays << "\n>" << buffer);
+ isEqual = false;
+ }
+ // Iterate all of the relations of "type" in the changeset
+ QSet<long> missingRelations;
+ for (ChangesetElementMap::iterator it = _relations[changeset_type].begin(); it != _relations[changeset_type].end(); ++it)
+ {
+ ChangesetRelation* relation = dynamic_cast<ChangesetRelation*>(it->second.get());
+ ChangesetElementMap::const_iterator found = changeset._relations[changeset_type].find(relation->id());
+ if (found != changeset._relations[changeset_type].end())
+ {
+ // Compare the two ways
+ ChangesetRelation* relation2 = dynamic_cast<ChangesetRelation*>(found->second.get());
+ QString output;
+ if (!relation->diff(*relation2, output))
+ {
+ // Display the relation diff
+ output.chop(1);
+ LOG_WARN("Relation ID " << relation->id() << "\n" << output);
+ }
+ }
+ else
+ missingRelations.insert(relation->id());
+ }
+ // Output missing relations in full
+ if (missingRelations.size() > 0)
+ {
+ QString buffer;
+ QTextStream ts(&buffer);
+ ts.setCodec("UTF-8");
+ for (QSet<long>::iterator it = missingRelations.begin(); it != missingRelations.end(); ++it)
+ ts << _relations[changeset_type][*it]->toString(0);
+ buffer.chop(1);
+ buffer.replace("\n", "\n>");
+ LOG_WARN("Missing relations: " << getString(static_cast<ChangesetType>(changeset_type)) <<
+ " - " << missingRelations << "\n>" << buffer);
+ isEqual = false;
+ }
+ }
+ return isEqual;
}
ChangesetInfo::ChangesetInfo()