healthCheck method

  1. @visibleForTesting
List<String> healthCheck()

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;
}