healthCheck method
Health check for the QuadTree node. Returns a list of problems found in the QuadTree.
Main purpose is to check if the QuadTree is in a valid state. You should not rely on this method for production code as it is very-verry slow and expensive. Better to use this method for debugging and testing.
Should be called only after optimize method.
Implementation
@visibleForTesting
List<String> healthCheck() {
final errors = <String>[];
if (capacity < 6) errors.add('Capacity must be greater or equal than 6.');
final nodeIds = <int>{};
if (_root?._dirty ?? false)
errors.add('Root node is dirty (call optimize).');
//final objects = <int>{};
visit((node) {
if (nodeIds.contains(node.id))
errors.add('Node #${node.id} is visited more than once or duplicated.');
nodeIds.add(node.id);
if (!identical(_nodes[node.id], node))
errors.add('Node #${node.id} is not stored in the nodes array.');
if (node._dirty) {
errors.add('Node #${node.id} is dirty (call optimize).');
}
if (node.leaf) {
if (node._subdivided)
errors.add('Leaf node #${node.id} is subdivided.');
if (node.length > capacity && node.depth < depth)
errors.add('Leaf node #${node.id} has too many objects.');
if (node._ids.length != node.length)
errors.add('Leaf node #${node.id} has invalid objects count.');
for (final objectId in node._ids) {
if (objectId >= _nextObjectId)
errors.add('Leaf node #${node.id} has invalid object id.');
if (_id2node[objectId] != node.id)
errors.add('Leaf node #${node.id} has invalid object reference.');
}
var child = node;
var parent = node.parent;
while (true) {
if (parent == null) {
if (!identical(child, _root))
errors.add('Leaf node #${child.id} has no parent.');
break; // Root node
}
if (child._length > parent.length)
errors.add('Leaf node #${child.id} has more objects than parent.');
if (!nodeIds.contains(parent.id))
errors.add('Parent node #${parent.id} is not visited.');
child = parent;
parent = parent.parent;
}
} else if (node.subdivided) {
if (!node._subdivided)
errors.add('Subdivided node #${node.id} is not subdivided.');
if (node._ids.isNotEmpty)
errors.add('Subdivided node #${node.id} has objects.');
if (node._length < 1)
errors.add('Subdivided node #${node.id} is empty (call optimize).');
if (node._length < capacity) {
if (node._northWest!.subdivided)
errors.add('Subdivided node #${node.id} is not optimized.');
else if (node._northEast!.subdivided)
errors.add('Subdivided node #${node.id} is not optimized.');
else if (node._southWest!.subdivided)
errors.add('Subdivided node #${node.id} is not optimized.');
else if (node._southEast!.subdivided)
errors.add('Subdivided node #${node.id} is not optimized.');
}
if (node._ids.isNotEmpty)
errors.add('Subdivided node #${node.id} has non-empty objects set.');
}
return true; // Continue visiting
});
// Check if all nodes are visited
if (nodes != nodeIds.length)
errors.add('Invalid nodes count: $nodes != ${nodeIds.length}.');
return errors;
}