From f532d8abc5d5422fe6c3ee5c80fbf81a1a824467 Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Thu, 2 Nov 2023 14:24:29 -0500 Subject: [PATCH 01/16] Migrate away from parts and only export the files we intend consumers to use --- .gitignore | 3 ++- example/main.dart | 5 ++++- lib/r_tree.dart | 12 +++--------- lib/src/r_tree/leaf_node.dart | 8 ++++++-- lib/src/r_tree/node.dart | 20 +++++++++++++------- lib/src/r_tree/non_leaf_node.dart | 9 +++++++-- lib/src/r_tree/quickselect.dart | 3 ++- lib/src/r_tree/r_tree.dart | 13 ++++++++++--- lib/src/r_tree/r_tree_contributor.dart | 16 +--------------- lib/src/r_tree/r_tree_datum.dart | 4 +++- lib/src/r_tree/rectangle_helper.dart | 17 +++++++++++++++++ test/r_tree/leaf_node_test.dart | 4 ++-- test/r_tree/node_test.dart | 3 +-- test/r_tree/non_leaf_node_test.dart | 3 +++ test/r_tree/r_tree_test.dart | 3 +++ 15 files changed, 77 insertions(+), 46 deletions(-) create mode 100644 lib/src/r_tree/rectangle_helper.dart diff --git a/.gitignore b/.gitignore index 29c6e6e..bfdf2c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store .idea/ *.iml *.iws @@ -12,4 +13,4 @@ build .dart_tool # coverage -/coverage/ \ No newline at end of file +/coverage/ diff --git a/example/main.dart b/example/main.dart index 3ef003a..d247699 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,7 +1,10 @@ import 'dart:async'; -import 'dart:html'; +import 'dart:html' hide Node; import 'dart:math'; import 'package:r_tree/r_tree.dart'; +import 'package:r_tree/src/r_tree/leaf_node.dart'; +import 'package:r_tree/src/r_tree/node.dart'; +import 'package:r_tree/src/r_tree/non_leaf_node.dart'; Future main() async { var rtree = RTree(); diff --git a/lib/r_tree.dart b/lib/r_tree.dart index 5e17cdf..46c2c5c 100644 --- a/lib/r_tree.dart +++ b/lib/r_tree.dart @@ -26,12 +26,6 @@ /// rectangles or polygons." - http://en.wikipedia.org/wiki/R-tree library r_tree; -import 'dart:math'; - -part 'src/r_tree/leaf_node.dart'; -part 'src/r_tree/non_leaf_node.dart'; -part 'src/r_tree/node.dart'; -part 'src/r_tree/quickselect.dart'; -part 'src/r_tree/r_tree.dart'; -part 'src/r_tree/r_tree_datum.dart'; -part 'src/r_tree/r_tree_contributor.dart'; +export 'src/r_tree/r_tree.dart'; +export 'src/r_tree/r_tree_datum.dart'; +export 'src/r_tree/r_tree_contributor.dart'; diff --git a/lib/src/r_tree/leaf_node.dart b/lib/src/r_tree/leaf_node.dart index a760d69..4d5fdf8 100644 --- a/lib/src/r_tree/leaf_node.dart +++ b/lib/src/r_tree/leaf_node.dart @@ -14,7 +14,11 @@ * limitations under the License. */ -part of r_tree; +import 'dart:math'; + +import 'package:r_tree/src/r_tree/node.dart'; +import 'package:r_tree/src/r_tree/r_tree_datum.dart'; +import 'package:r_tree/src/r_tree/rectangle_helper.dart'; /// A [Node] that is a leaf node of the tree. These are created automatically /// by [RTree] when inserting/removing items from the tree. @@ -57,7 +61,7 @@ class LeafNode extends Node { } clearChildren() { + super.clearChildren(); _items.clear(); - _minimumBoundingRect = null; } } diff --git a/lib/src/r_tree/node.dart b/lib/src/r_tree/node.dart index 14361d3..eee9212 100644 --- a/lib/src/r_tree/node.dart +++ b/lib/src/r_tree/node.dart @@ -14,7 +14,11 @@ * limitations under the License. */ -part of r_tree; +import 'dart:math'; + +import 'package:r_tree/src/r_tree/r_tree_contributor.dart'; +import 'package:r_tree/src/r_tree/r_tree_datum.dart'; +import 'package:r_tree/src/r_tree/rectangle_helper.dart'; const noMBR = Rectangle(0, 0, 0, 0); @@ -42,6 +46,8 @@ abstract class Node implements RTreeContributor { return _minimumBoundingRect ?? noMBR; } + void setRect(Rectangle rect) => _minimumBoundingRect = rect; + Node(this.branchFactor); /// Returns an iterable of all items within [searchRect] @@ -54,7 +60,9 @@ abstract class Node implements RTreeContributor { remove(RTreeDatum item); /// Remove all children from this node - clearChildren(); + clearChildren() { + _minimumBoundingRect = null; + } /// Returns a list of all items in this node List get children; @@ -81,14 +89,14 @@ abstract class Node implements RTreeContributor { /// of adding a new @item to this Node num expansionCost(RTreeContributor item) { if (_minimumBoundingRect == null) { - return _area(item.rect); + return item.rect.area(); } Rectangle newRect = rect.boundingBox(item.rect); - return _area(newRect) - _area(rect); + return newRect.area() - rect.area(); } - num area() => _area(rect); + num area() => rect.area(); num get margin => (rect.right - rect.left) + (rect.bottom - rect.top); @@ -212,5 +220,3 @@ class _Seeds { const _Seeds(this.seed1, this.seed2); } - -num _area(Rectangle rect) => rect.width * rect.height; diff --git a/lib/src/r_tree/non_leaf_node.dart b/lib/src/r_tree/non_leaf_node.dart index a0a1f1f..5152687 100644 --- a/lib/src/r_tree/non_leaf_node.dart +++ b/lib/src/r_tree/non_leaf_node.dart @@ -14,7 +14,12 @@ * limitations under the License. */ -part of r_tree; +import 'dart:math'; + +import 'package:r_tree/src/r_tree/leaf_node.dart'; +import 'package:r_tree/src/r_tree/node.dart'; +import 'package:r_tree/src/r_tree/r_tree_datum.dart'; +import 'package:r_tree/src/r_tree/rectangle_helper.dart'; /// A [Node] that is not a leaf end of the [RTree]. These are created automatically /// by [RTree] when inserting/removing items from the tree. @@ -100,8 +105,8 @@ class NonLeafNode extends Node { } clearChildren() { + super.clearChildren(); _childNodes.clear(); - _minimumBoundingRect = null; } Node _getBestNodeForInsert(RTreeDatum item) { diff --git a/lib/src/r_tree/quickselect.dart b/lib/src/r_tree/quickselect.dart index 3018104..324d432 100644 --- a/lib/src/r_tree/quickselect.dart +++ b/lib/src/r_tree/quickselect.dart @@ -1,8 +1,9 @@ -part of r_tree; // Port of https://github.com/mourner/quickselect. // sort an array so that items come in groups of n unsorted items, with groups sorted between each other; // combines selection algorithm with binary divide & conquer approach +import 'dart:math'; + multiSelect(List arr, int left, int right, int n, int Function(E a, E b) compare) { final stack = [left, right]; diff --git a/lib/src/r_tree/r_tree.dart b/lib/src/r_tree/r_tree.dart index 75621c7..12d165a 100644 --- a/lib/src/r_tree/r_tree.dart +++ b/lib/src/r_tree/r_tree.dart @@ -14,7 +14,14 @@ * limitations under the License. */ -part of r_tree; +import 'dart:math'; + +import 'package:r_tree/src/r_tree/leaf_node.dart'; +import 'package:r_tree/src/r_tree/node.dart'; +import 'package:r_tree/src/r_tree/non_leaf_node.dart'; +import 'package:r_tree/src/r_tree/quickselect.dart'; +import 'package:r_tree/src/r_tree/r_tree_datum.dart'; +import 'package:r_tree/src/r_tree/rectangle_helper.dart'; /// A two dimensional index of data that allows querying by rectangular areas class RTree { @@ -255,7 +262,7 @@ class RTree { final bbox2 = _boundingBoxForDistribution(node, i, M); final intersection = bbox1.rect.intersection(bbox2.rect); - final overlap = intersection != null ? _area(intersection) : 0; + final overlap = intersection != null ? intersection.area() : 0; final area = bbox1.area() + bbox2.area(); // choose distribution with minimum overlap @@ -318,7 +325,7 @@ class RTree { Node _boundingBoxForDistribution(Node node, int startChild, int stopChild) { final destNode = LeafNode(_branchFactor); - destNode._minimumBoundingRect = node.children[0].rect; + destNode.setRect(node.children[0].rect); for (int i = startChild; i < stopChild; i++) { destNode.extend(node.children[i].rect); diff --git a/lib/src/r_tree/r_tree_contributor.dart b/lib/src/r_tree/r_tree_contributor.dart index c1a4ef3..b940e00 100644 --- a/lib/src/r_tree/r_tree_contributor.dart +++ b/lib/src/r_tree/r_tree_contributor.dart @@ -14,7 +14,7 @@ * limitations under the License. */ -part of r_tree; +import 'dart:math'; /// The base definition of an object that exists in an [RTree] abstract class RTreeContributor { @@ -22,17 +22,3 @@ abstract class RTreeContributor { Rectangle get rect; } - -extension on Rectangle { - // Calculate if otherRect overlaps with the current rectangle - // - // This function is a replication of Rectangle.intersects. It differs in that - // the inequalities are strict and do not allow for equivalences. This means - // that the two rectangles are not considered overlapping if they share an edge. - bool overlaps(Rectangle otherRect) { - return left < otherRect.left + otherRect.width && - otherRect.left < left + width && - top < otherRect.top + otherRect.height && - otherRect.top < top + height; - } -} diff --git a/lib/src/r_tree/r_tree_datum.dart b/lib/src/r_tree/r_tree_datum.dart index baf4da4..2b4210e 100644 --- a/lib/src/r_tree/r_tree_datum.dart +++ b/lib/src/r_tree/r_tree_datum.dart @@ -14,7 +14,9 @@ * limitations under the License. */ -part of r_tree; +import 'dart:math'; + +import 'package:r_tree/src/r_tree/r_tree_contributor.dart'; /// An [RTreeContributor] that has a piece of data attached to it class RTreeDatum implements RTreeContributor { diff --git a/lib/src/r_tree/rectangle_helper.dart b/lib/src/r_tree/rectangle_helper.dart new file mode 100644 index 0000000..e655661 --- /dev/null +++ b/lib/src/r_tree/rectangle_helper.dart @@ -0,0 +1,17 @@ +import 'dart:math'; + +extension RectangleHelper on Rectangle { + /// Calculate if otherRect overlaps with the current rectangle + /// + /// This function is a replication of Rectangle.intersects. It differs in that + /// the inequalities are strict and do not allow for equivalences. This means + /// that the two rectangles are not considered overlapping if they share an edge. + bool overlaps(Rectangle otherRect) { + return left < otherRect.left + otherRect.width && + otherRect.left < left + width && + top < otherRect.top + otherRect.height && + otherRect.top < top + height; + } + + num area() => width * height; +} diff --git a/test/r_tree/leaf_node_test.dart b/test/r_tree/leaf_node_test.dart index 5b8d7cb..3439e0c 100644 --- a/test/r_tree/leaf_node_test.dart +++ b/test/r_tree/leaf_node_test.dart @@ -1,8 +1,8 @@ -library leaf_node; - import 'dart:math'; import 'package:r_tree/r_tree.dart'; +import 'package:r_tree/src/r_tree/leaf_node.dart'; +import 'package:r_tree/src/r_tree/node.dart'; import 'package:test/test.dart'; main() { diff --git a/test/r_tree/node_test.dart b/test/r_tree/node_test.dart index dc69de7..20bc1bf 100644 --- a/test/r_tree/node_test.dart +++ b/test/r_tree/node_test.dart @@ -1,8 +1,7 @@ -library node_test; - import 'dart:math'; import 'package:r_tree/r_tree.dart'; +import 'package:r_tree/src/r_tree/leaf_node.dart'; import 'package:test/test.dart'; main() { diff --git a/test/r_tree/non_leaf_node_test.dart b/test/r_tree/non_leaf_node_test.dart index 0d7d395..8fd519b 100644 --- a/test/r_tree/non_leaf_node_test.dart +++ b/test/r_tree/non_leaf_node_test.dart @@ -3,6 +3,9 @@ library non_leaf_node; import 'dart:math'; import 'package:r_tree/r_tree.dart'; +import 'package:r_tree/src/r_tree/leaf_node.dart'; +import 'package:r_tree/src/r_tree/node.dart'; +import 'package:r_tree/src/r_tree/non_leaf_node.dart'; import 'package:test/test.dart'; main() { diff --git a/test/r_tree/r_tree_test.dart b/test/r_tree/r_tree_test.dart index 79891f8..ae00c7b 100644 --- a/test/r_tree/r_tree_test.dart +++ b/test/r_tree/r_tree_test.dart @@ -3,6 +3,9 @@ library r_tree; import 'dart:math'; import 'package:r_tree/r_tree.dart'; +import 'package:r_tree/src/r_tree/leaf_node.dart'; +import 'package:r_tree/src/r_tree/node.dart'; +import 'package:r_tree/src/r_tree/non_leaf_node.dart'; import 'package:test/test.dart'; main() { From a71b38770367f899fb26ff534faad75e29ce1c85 Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Thu, 2 Nov 2023 14:27:48 -0500 Subject: [PATCH 02/16] Trick to get example app access to non-public root node --- example/main.dart | 3 ++- lib/r_tree.dart | 6 +++--- lib/src/r_tree/r_tree.dart | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/example/main.dart b/example/main.dart index d247699..a87f63b 100644 --- a/example/main.dart +++ b/example/main.dart @@ -5,6 +5,7 @@ import 'package:r_tree/r_tree.dart'; import 'package:r_tree/src/r_tree/leaf_node.dart'; import 'package:r_tree/src/r_tree/node.dart'; import 'package:r_tree/src/r_tree/non_leaf_node.dart'; +import 'package:r_tree/src/r_tree/r_tree.dart'; Future main() async { var rtree = RTree(); @@ -156,7 +157,7 @@ Future main() async { querySelector('#graphviz')!.onClick.listen((_) { var output = querySelector('#output') as PreElement; - output.innerHtml = toGraphViz(rtree.currentRootNode); + output.innerHtml = toGraphViz(getCurrentRootNode(rtree)); }); querySelector('#copy')!.onClick.listen((_) async { diff --git a/lib/r_tree.dart b/lib/r_tree.dart index 46c2c5c..fe6b562 100644 --- a/lib/r_tree.dart +++ b/lib/r_tree.dart @@ -26,6 +26,6 @@ /// rectangles or polygons." - http://en.wikipedia.org/wiki/R-tree library r_tree; -export 'src/r_tree/r_tree.dart'; -export 'src/r_tree/r_tree_datum.dart'; -export 'src/r_tree/r_tree_contributor.dart'; +export 'src/r_tree/r_tree.dart' show RTree; +export 'src/r_tree/r_tree_datum.dart' show RTreeDatum; +export 'src/r_tree/r_tree_contributor.dart' show RTreeContributor; diff --git a/lib/src/r_tree/r_tree.dart b/lib/src/r_tree/r_tree.dart index 12d165a..d9cc74b 100644 --- a/lib/src/r_tree/r_tree.dart +++ b/lib/src/r_tree/r_tree.dart @@ -37,8 +37,6 @@ class RTree { _resetRoot(); } - Node get currentRootNode => _root; - /// Removes [item] from the rtree remove(RTreeDatum item) { _root.remove(item); @@ -358,3 +356,6 @@ int _compareRectTop(RTreeDatum a, RTreeDatum b) => _compareNumber(a.rect.top, b. @pragma('vm:prefer-inline') int _compareRectLeft(RTreeDatum a, RTreeDatum b) => _compareNumber(a.rect.left, b.rect.left); + +/// Helper for example app to generate GraphViz +Node getCurrentRootNode(RTree tree) => tree._root; From 83af46a89c12e1341b13f61771f1a443a2d1b0fe Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Fri, 3 Nov 2023 13:02:42 -0500 Subject: [PATCH 03/16] Enforce and fix all recommended lints --- analysis_options.yaml | 5 +--- benchmark/benchmarks.dart | 16 +++++++++-- example/main.dart | 25 ++++++++++-------- lib/src/r_tree/leaf_node.dart | 6 +++++ lib/src/r_tree/node.dart | 1 + lib/src/r_tree/non_leaf_node.dart | 10 ++++++- lib/src/r_tree/r_tree.dart | 24 +++++++++-------- lib/src/r_tree/r_tree_datum.dart | 3 ++- pubspec.yaml | 1 + test/r_tree/node_test.dart | 8 +++--- test/r_tree/r_tree_test.dart | 44 +++++++++++++++---------------- 11 files changed, 87 insertions(+), 56 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index df4a168..572dd23 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1 @@ -linter: - rules: - - cancel_subscriptions - - close_sinks \ No newline at end of file +include: package:lints/recommended.yaml diff --git a/benchmark/benchmarks.dart b/benchmark/benchmarks.dart index e6fcb10..e1e969c 100644 --- a/benchmark/benchmarks.dart +++ b/benchmark/benchmarks.dart @@ -39,7 +39,7 @@ main() { collector.collected.forEach((String name, double value) { name += (' ' * (longestName - name.length)); var valueString = value.toStringAsFixed(2); - output += '$name\t${' ' * (longestValue.length - valueString.length)}${valueString}\n'; + output += '$name\t${' ' * (longestValue.length - valueString.length)}$valueString\n'; }); print(output); @@ -53,6 +53,7 @@ class InsertBenchmark extends RTreeBenchmarkBase { late RTree tree; late List> datum; + @override void run() { tree = RTree(branchFactor); for (var data in datum) { @@ -60,6 +61,7 @@ class InsertBenchmark extends RTreeBenchmarkBase { } } + @override void setup() { Random rand = Random(randomSeed); datum = >[]; @@ -73,6 +75,7 @@ class InsertBenchmark extends RTreeBenchmarkBase { } } + @override void teardown() {} } @@ -84,11 +87,13 @@ class LoadBenchmark extends RTreeBenchmarkBase { late RTree tree; late List> datum; + @override void run() { tree = RTree(branchFactor); tree.load(datum); } + @override void setup() { Random rand = Random(randomSeed); datum = >[]; @@ -103,6 +108,7 @@ class LoadBenchmark extends RTreeBenchmarkBase { datum.shuffle(); } + @override void teardown() {} } @@ -112,6 +118,7 @@ class RemoveBenchmark extends RTreeBenchmarkBase { late RTree tree; final items = >>[]; + @override void run() { for (int i = 0; i < 100; i++) { for (int j = 0; j < 50; j++) { @@ -120,6 +127,7 @@ class RemoveBenchmark extends RTreeBenchmarkBase { } } + @override void setup() { tree = RTree(branchFactor); @@ -137,6 +145,7 @@ class RemoveBenchmark extends RTreeBenchmarkBase { } } + @override void teardown() {} } @@ -154,12 +163,13 @@ class SearchBenchmark extends RTreeBenchmarkBase { required this.totalItems, this.iterateAll = false, this.useLoad = false, - }) : super("Search${iterateAll ? '/Iterate' : ''} ${useLoad ? '(using Load)' : '(using Insert)'} ${totalItems}", + }) : super("Search${iterateAll ? '/Iterate' : ''} ${useLoad ? '(using Load)' : '(using Insert)'} $totalItems", collector); late RTree tree; late int size; + @override void run() { for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { @@ -174,6 +184,7 @@ class SearchBenchmark extends RTreeBenchmarkBase { } } + @override void setup() { size = sqrt(totalItems).ceil(); tree = RTree(branchFactor); @@ -202,6 +213,7 @@ class SearchBenchmark extends RTreeBenchmarkBase { } } + @override void teardown() {} } diff --git a/example/main.dart b/example/main.dart index 3ef003a..1dd7617 100644 --- a/example/main.dart +++ b/example/main.dart @@ -13,7 +13,7 @@ Future main() async { ..fillRect(0, 0, 640, 480); int? startX, startY, proposedX, proposedY; - final draw = () { + draw() { canvas.context2D.clearRect(0, 0, 700, 500); canvas.context2D.strokeStyle = ''; rtree.search(Rectangle(0, 0, 700, 500)).forEach((node) { @@ -26,7 +26,7 @@ Future main() async { canvas.context2D.strokeStyle = 'black'; canvas.context2D.strokeRect(startX!, startY!, proposedX! - startX!, proposedY! - startY!); } - }; + } var isDrawing = false; canvas.onMouseDown.listen((MouseEvent event) { @@ -96,22 +96,25 @@ Future main() async { final blueButton = querySelector('#blue')!; final searchButton = querySelector('#search')!; final allButtons = [redButton, greenButton, blueButton, searchButton]; - final resetAllButtons = () => allButtons.forEach((element) { - element.style.background = ''; - }); + resetAllButtons() { + for (final element in allButtons) { + element.style.background = ''; + } + } + redButton.onClick.listen((_) { resetAllButtons(); - currentBrush = '$red'; + currentBrush = red; redButton.style.background = 'darkgray'; }); greenButton.onClick.listen((_) { resetAllButtons(); - currentBrush = '$green'; + currentBrush = green; greenButton.style.background = 'darkgray'; }); blueButton.onClick.listen((_) { resetAllButtons(); - currentBrush = '$blue'; + currentBrush = blue; blueButton.style.background = 'darkgray'; }); searchButton.onClick.listen((_) { @@ -120,7 +123,7 @@ Future main() async { searchButton.style.background = 'darkgray'; }); - final makeDataset = () { + makeDataset() { Random rand = Random(); var datum = >[]; for (int i = 0; i < 300; i++) { @@ -133,7 +136,7 @@ Future main() async { datum.add(item); } return datum; - }; + } querySelector('#insert')!.onClick.listen((_) { makeDataset().forEach(rtree.insert); @@ -173,7 +176,7 @@ const String red = '#ff0000$alpha'; const String green = '#00ff00$alpha'; const String blue = '#0000ff$alpha'; const colors = [red, green, blue]; -String currentBrush = '$red'; +String currentBrush = red; String toGraphViz(Node root) { var output = StringBuffer('''digraph r_tree { diff --git a/lib/src/r_tree/leaf_node.dart b/lib/src/r_tree/leaf_node.dart index 330172b..2ee7de6 100644 --- a/lib/src/r_tree/leaf_node.dart +++ b/lib/src/r_tree/leaf_node.dart @@ -20,6 +20,7 @@ part of r_tree; /// by [RTree] when inserting/removing items from the tree. class LeafNode extends Node { late final List> _items; + @override List> get children => _items; LeafNode(int branchFactor, {List>? initialItems}) : super(branchFactor) { @@ -38,24 +39,29 @@ class LeafNode extends Node { @override int get height => 1; + @override Node createNewNode() { return LeafNode(branchFactor); } + @override Iterable> search(Rectangle searchRect, bool Function(E item)? shouldInclude) { return _items.where( (RTreeDatum item) => item.rect.overlaps(searchRect) && (shouldInclude == null || shouldInclude(item.value))); } + @override Node? insert(RTreeDatum item) { addChild(item); return splitIfNecessary(); } + @override remove(RTreeDatum item) { removeChild(item); } + @override clearChildren() { _items.clear(); _minimumBoundingRect = noMBR; diff --git a/lib/src/r_tree/node.dart b/lib/src/r_tree/node.dart index 8c9b52d..3fc8417 100644 --- a/lib/src/r_tree/node.dart +++ b/lib/src/r_tree/node.dart @@ -33,6 +33,7 @@ abstract class Node implements RTreeContributor { Rectangle _minimumBoundingRect = noMBR; /// Returns the rectangle this Node covers + @override Rectangle get rect => _minimumBoundingRect; Node(this.branchFactor); diff --git a/lib/src/r_tree/non_leaf_node.dart b/lib/src/r_tree/non_leaf_node.dart index 0da59c6..07d3105 100644 --- a/lib/src/r_tree/non_leaf_node.dart +++ b/lib/src/r_tree/non_leaf_node.dart @@ -20,6 +20,7 @@ part of r_tree; /// by [RTree] when inserting/removing items from the tree. class NonLeafNode extends Node { late final List> _childNodes; + @override List> get children => _childNodes; NonLeafNode(int branchFactor, {List>? initialChildNodes}) : super(branchFactor) { @@ -34,10 +35,12 @@ class NonLeafNode extends Node { } } + @override Node createNewNode() { return NonLeafNode(branchFactor); } + @override Iterable> search(Rectangle searchRect, bool Function(E item)? shouldInclude) { List> overlappingLeafs = []; @@ -50,6 +53,7 @@ class NonLeafNode extends Node { return overlappingLeafs; } + @override Node? insert(RTreeDatum item) { include(item); @@ -63,6 +67,7 @@ class NonLeafNode extends Node { return splitIfNecessary(); } + @override remove(RTreeDatum item) { List> childrenToRemove = []; @@ -83,11 +88,13 @@ class NonLeafNode extends Node { _updateHeightAndBounds(); } + @override addChild(Node child) { super.addChild(child); child.parent = this; } + @override removeChild(Node child) { super.removeChild(child); child.parent = null; @@ -95,6 +102,7 @@ class NonLeafNode extends Node { _updateHeightAndBounds(); } + @override clearChildren() { _childNodes.clear(); _minimumBoundingRect = noMBR; @@ -121,7 +129,7 @@ class NonLeafNode extends Node { for (final childNode in _childNodes) { maxChildHeight = max(maxChildHeight, childNode.height); } - this.height = 1 + maxChildHeight; + height = 1 + maxChildHeight; updateBoundingRect(); } diff --git a/lib/src/r_tree/r_tree.dart b/lib/src/r_tree/r_tree.dart index 1fa1a29..68db2f8 100644 --- a/lib/src/r_tree/r_tree.dart +++ b/lib/src/r_tree/r_tree.dart @@ -36,7 +36,7 @@ class RTree { remove(RTreeDatum item) { _root.remove(item); - if (_root.children.length == 0) { + if (_root.children.isEmpty) { _resetRoot(); } } @@ -79,7 +79,7 @@ class RTree { // recursively build the tree with the given data from scratch using OMT algorithm Node node = _build(items, 0, items.length - 1, 0); - if (_root.children.length == 0) { + if (_root.children.isEmpty) { // save as is if tree is empty _root = node; } else if (_root.height == node.height) { @@ -121,7 +121,9 @@ class RTree { } // fix all the bounding rectangles along the insertion path - insertPath.forEach((e) => e.updateBoundingRect()); + for (var e in insertPath) { + e.updateBoundingRect(); + } } Node _chooseSubtree(Node inode, Node node, int level, List> path) { @@ -192,18 +194,18 @@ class RTree { // split the items into M mostly square tiles - final N2 = (N.toDouble() / M).ceil(); - final N1 = N2 * sqrt(M).ceil(); + final n2 = (N.toDouble() / M).ceil(); + final n1 = n2 * sqrt(M).ceil(); - multiSelect(items, left, right, N1, _compareRectLeft); + multiSelect(items, left, right, n1, _compareRectLeft); - for (int i = left; i <= right; i += N1) { - final right2 = min(i + N1 - 1, right); + for (int i = left; i <= right; i += n1) { + final right2 = min(i + n1 - 1, right); - multiSelect(items, i, right2, N2, _compareRectTop); + multiSelect(items, i, right2, n2, _compareRectTop); - for (int j = i; j <= right2; j += N2) { - final right3 = min(j + N2 - 1, right2); + for (int j = i; j <= right2; j += n2) { + final right3 = min(j + n2 - 1, right2); // pack each entry recursively node.children.add(_build(items, j, right3, height - 1, node)); diff --git a/lib/src/r_tree/r_tree_datum.dart b/lib/src/r_tree/r_tree_datum.dart index baf4da4..758a00e 100644 --- a/lib/src/r_tree/r_tree_datum.dart +++ b/lib/src/r_tree/r_tree_datum.dart @@ -18,8 +18,9 @@ part of r_tree; /// An [RTreeContributor] that has a piece of data attached to it class RTreeDatum implements RTreeContributor { + @override final Rectangle rect; final E value; - RTreeDatum(Rectangle this.rect, E this.value); + RTreeDatum(this.rect, this.value); } diff --git a/pubspec.yaml b/pubspec.yaml index 31af6b2..cbecb7e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,4 +13,5 @@ dev_dependencies: build_web_compilers: ^3.2.7 dart_style: ^2.2.4 dependency_validator: ^3.0.0 + lints: ^2.0.1 test: ^1.15.7 diff --git a/test/r_tree/node_test.dart b/test/r_tree/node_test.dart index dc69de7..8a61539 100644 --- a/test/r_tree/node_test.dart +++ b/test/r_tree/node_test.dart @@ -10,7 +10,7 @@ main() { group('splitIfNecessary', () { test('split should not occur until branchFactor is exceeded', () { LeafNode leafNode = LeafNode(10); - Map itemMap = Map(); + Map itemMap = {}; for (int i = 0; i < 4; i++) { String itemId = 'Item $i'; @@ -24,7 +24,7 @@ main() { test('test that split correctly splits a column', () { LeafNode leafNode = LeafNode(3); - Map itemMap = Map(); + Map itemMap = {}; for (int i = 0; i < 4; i++) { String itemId = 'Item $i'; @@ -51,7 +51,7 @@ main() { test('test that split correctly splits a row', () { LeafNode leafNode = LeafNode(3); - Map itemMap = Map(); + Map itemMap = {}; for (int i = 0; i < 4; i++) { String itemId = 'Item $i'; @@ -78,7 +78,7 @@ main() { test('test that split correctly splits a random cluster', () { LeafNode leafNode = LeafNode(3); - Map itemMap = Map(); + Map itemMap = {}; for (int i = 0; i < 4; i++) { String itemId = 'Item $i'; diff --git a/test/r_tree/r_tree_test.dart b/test/r_tree/r_tree_test.dart index 8c97367..66c5e7c 100644 --- a/test/r_tree/r_tree_test.dart +++ b/test/r_tree/r_tree_test.dart @@ -22,20 +22,20 @@ main() { expect(items.length, equals(1)); expect(items.elementAt(0).value, equals('Item 1')); - items.forEach((item) { + for (var i = 0; i < items.length; i++) { tree.insert(RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 2')); tree.insert(RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 3')); tree.insert(RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 4')); tree.insert(RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 5')); - }); + } assertTreeValidity(tree); items = tree.search(item.rect); expect(items.length, equals(5)); - items.forEach((item) { + for (var item in items) { tree.remove(item); - }); + } assertTreeValidity(tree); items = tree.search((item.rect)); @@ -54,7 +54,7 @@ main() { for (final addMethod in addMethods) { test('search for 1 cell in large format ranges (${addMethod.name})', () { RTree tree = RTree(3); - Map itemMap = Map(); + Map itemMap = {}; List> itemsToInsert = []; for (int i = 0; i < 10; i++) { @@ -85,7 +85,7 @@ main() { test('insert enough items to cause split (${addMethod.name})', () { RTree tree = RTree(3); - Map itemMap = Map(); + Map itemMap = {}; List> itemsToInsert = []; for (int i = 0; i < 5; i++) { @@ -172,7 +172,7 @@ main() { test('remove from large tree', () { RTree tree = RTree(16); - Map itemMap = Map(); + Map itemMap = {}; for (int i = 0; i < 50; i++) { for (int j = 0; j < 50; j++) { @@ -220,9 +220,9 @@ main() { var items = tree.search(Rectangle(0, 0, 50, 50)); expect(items.length, equals(2500)); - data.forEach((RTreeDatum item) { + for (var item in data) { tree.remove(item); - }); + } assertTreeValidity(tree); items = tree.search(Rectangle(0, 0, 50, 50)); @@ -236,9 +236,9 @@ main() { items = tree.search(Rectangle(0, 0, 50, 50)); - items.forEach((datum) { + for (var datum in items) { expect(datum.value, equals('New Initial Item')); - }); + } }); test('remove all items and then reload', () { @@ -285,7 +285,7 @@ void assertTreeValidity(RTree tree) { /// Comprehensively assert the consistency of the specified subtree, including node height, parent references, and /// bounding rectangles. -_SubtreeValidationData assertNodeValidity(RTree tree, RTreeContributor contributor) { +SubtreeValidationData assertNodeValidity(RTree tree, RTreeContributor contributor) { if (contributor is LeafNode) { return assertLeafNodeValidity(tree, contributor); } else if (contributor is NonLeafNode) { @@ -293,12 +293,12 @@ _SubtreeValidationData assertNodeValidity(RTree tree, RTreeContributor con } // This is a datum - return _SubtreeValidationData(0, contributor.rect); + return SubtreeValidationData(0, contributor.rect); } /// Comprehensively assert the consistency of the subtree rooted at the specified leaf node, including node height, /// parent references, and bounding rectangles. -_SubtreeValidationData assertLeafNodeValidity(RTree tree, LeafNode node) { +SubtreeValidationData assertLeafNodeValidity(RTree tree, LeafNode node) { if (node.height != 1) { throw StateError('Leaf height of ${node.height} should be 1.'); } @@ -313,22 +313,22 @@ _SubtreeValidationData assertLeafNodeValidity(RTree tree, LeafNode node throw StateError('Leaf rect ${node.rect} should be $actualRect.'); } - return _SubtreeValidationData(1, actualRect); + return SubtreeValidationData(1, actualRect); } /// Comprehensively assert the consistency of the subtree rooted at the specified non-leaf node, including node height, /// parent references, and bounding rectangles. -_SubtreeValidationData assertNonLeafNodeValidity(RTree tree, NonLeafNode node) { +SubtreeValidationData assertNonLeafNodeValidity(RTree tree, NonLeafNode node) { if (node.children.isEmpty) { throw StateError('Non-leaf nodes must have at least one leaf.'); } // Assert parent references for children point back to this node - node.children.forEach((child) { + for (var child in node.children) { if (child.parent != node) { throw StateError("Non-leaf child's parent reference is incorrect."); } - }); + } // Traverse the tree from this child and collect validation data to propagate upwards final childrenValidationData = node.children.map((child) => assertNodeValidity(tree, child)).toList(); @@ -338,7 +338,7 @@ _SubtreeValidationData assertNonLeafNodeValidity(RTree tree, NonLeafNode(0, 0, 0, 0); // Recalculate the actual height for this subtree using validation data - final compareMaxWithChild = (int maxHeight, _SubtreeValidationData child) => max(maxHeight, child.height); + compareMaxWithChild(int maxHeight, SubtreeValidationData child) => max(maxHeight, child.height); final maxChildHeight = childrenValidationData.fold(0, compareMaxWithChild); // Assert this node's height matches its actual structure @@ -352,14 +352,14 @@ _SubtreeValidationData assertNonLeafNodeValidity(RTree tree, NonLeafNode rect; - _SubtreeValidationData(this.height, this.rect); + SubtreeValidationData(this.height, this.rect); } /// Serializes the tree in a human-readable form for debugging. From a45199e8d4eed3282f4037927cf7659d511461f0 Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Fri, 10 Nov 2023 12:08:42 -0600 Subject: [PATCH 04/16] More lints --- analysis_options.yaml | 6 +- benchmark/benchmarks.dart | 84 ++++++++++++++-------------- benchmark/web_benchmarks.dart | 2 +- example/main.dart | 72 ++++++++++++------------ lib/src/r_tree/leaf_node.dart | 8 +-- lib/src/r_tree/node.dart | 34 ++++++------ lib/src/r_tree/non_leaf_node.dart | 28 +++++----- lib/src/r_tree/quickselect.dart | 2 +- lib/src/r_tree/r_tree.dart | 18 +++--- pubspec.yaml | 1 + test/r_tree/leaf_node_test.dart | 10 ++-- test/r_tree/node_test.dart | 42 +++++++------- test/r_tree/non_leaf_node_test.dart | 12 ++-- test/r_tree/r_tree_test.dart | 85 +++++++++++++++-------------- 14 files changed, 205 insertions(+), 199 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 572dd23..803fc6a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1,5 @@ -include: package:lints/recommended.yaml +include: package:workiva_analysis_options/v2.recommended.yaml +#analyzer: +# strong-mode: +# implicit-casts: false +# implicit-dynamic: true diff --git a/benchmark/benchmarks.dart b/benchmark/benchmarks.dart index e1e969c..2f9b99f 100644 --- a/benchmark/benchmarks.dart +++ b/benchmark/benchmarks.dart @@ -4,11 +4,11 @@ import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:r_tree/r_tree.dart'; -final int branchFactor = 16; -final int randomSeed = 3; -main() { +const int branchFactor = 16; +const int randomSeed = 3; +void main() { print('Running benchmarks...'); - var collector = ScoreCollector(); + final collector = ScoreCollector(); InsertBenchmark(collector, totalItems: 100).report(); InsertBenchmark(collector, totalItems: 1000).report(); InsertBenchmark(collector, totalItems: 10000).report(); @@ -27,18 +27,18 @@ main() { SearchBenchmark(collector, totalItems: 1000, iterateAll: true, useLoad: true).report(); SearchBenchmark(collector, totalItems: 10000, iterateAll: true, useLoad: true).report(); - var longestName = + final longestName = collector.collected.keys.reduce((value, element) => value.length > element.length ? value : element).length; - var longestValue = collector.collected.values + final longestValue = collector.collected.values .reduce((value, element) => value.toStringAsFixed(2).length > element.toStringAsFixed(2).length ? value : element) .toStringAsFixed(2); - var nameHeading = 'Name'; - var heading = '$nameHeading${' ' * (longestName - nameHeading.length)}\tResult (microseconds)'; - var separator = '-' * (heading.length + 5); + const nameHeading = 'Name'; + final heading = '$nameHeading${' ' * (longestName - nameHeading.length)}\tResult (microseconds)'; + final separator = '-' * (heading.length + 5); var output = '\n$heading\n$separator\n'; - collector.collected.forEach((String name, double value) { - name += (' ' * (longestName - name.length)); - var valueString = value.toStringAsFixed(2); + collector.collected.forEach((name, value) { + name += ' ' * (longestName - name.length); + final valueString = value.toStringAsFixed(2); output += '$name\t${' ' * (longestValue.length - valueString.length)}$valueString\n'; }); @@ -48,7 +48,7 @@ main() { class InsertBenchmark extends RTreeBenchmarkBase { final int totalItems; - InsertBenchmark(ScoreCollector collector, {this.totalItems = 500}) : super("Insert $totalItems", collector); + InsertBenchmark(ScoreCollector collector, {this.totalItems = 500}) : super('Insert $totalItems', collector); late RTree tree; late List> datum; @@ -56,20 +56,20 @@ class InsertBenchmark extends RTreeBenchmarkBase { @override void run() { tree = RTree(branchFactor); - for (var data in datum) { + for (final data in datum) { tree.insert(data); } } @override void setup() { - Random rand = Random(randomSeed); + final rand = Random(randomSeed); datum = >[]; - for (int i = 0; i < totalItems; i++) { - int x = rand.nextInt(1000); - int y = rand.nextInt(1000); - int height = rand.nextInt(100); - int width = rand.nextInt(100); + for (var i = 0; i < totalItems; i++) { + final x = rand.nextInt(1000); + final y = rand.nextInt(1000); + final height = rand.nextInt(100); + final width = rand.nextInt(100); final item = RTreeDatum(Rectangle(x, y, width, height), 'item $i'); datum.add(item); } @@ -82,7 +82,7 @@ class InsertBenchmark extends RTreeBenchmarkBase { class LoadBenchmark extends RTreeBenchmarkBase { final int totalItems; - LoadBenchmark(ScoreCollector collector, {required this.totalItems}) : super("Load $totalItems ", collector); + LoadBenchmark(ScoreCollector collector, {required this.totalItems}) : super('Load $totalItems ', collector); late RTree tree; late List> datum; @@ -95,13 +95,13 @@ class LoadBenchmark extends RTreeBenchmarkBase { @override void setup() { - Random rand = Random(randomSeed); + final rand = Random(randomSeed); datum = >[]; - for (int i = 0; i < totalItems; i++) { - int x = rand.nextInt(1000); - int y = rand.nextInt(1000); - int height = rand.nextInt(100); - int width = rand.nextInt(100); + for (var i = 0; i < totalItems; i++) { + final x = rand.nextInt(1000); + final y = rand.nextInt(1000); + final height = rand.nextInt(100); + final width = rand.nextInt(100); final item = RTreeDatum(Rectangle(x, y, width, height), 'item $i'); datum.add(item); } @@ -113,15 +113,15 @@ class LoadBenchmark extends RTreeBenchmarkBase { } class RemoveBenchmark extends RTreeBenchmarkBase { - RemoveBenchmark(ScoreCollector collector) : super("Remove 5k", collector); + RemoveBenchmark(ScoreCollector collector) : super('Remove 5k', collector); late RTree tree; final items = >>[]; @override void run() { - for (int i = 0; i < 100; i++) { - for (int j = 0; j < 50; j++) { + for (var i = 0; i < 100; i++) { + for (var j = 0; j < 50; j++) { tree.remove(items[i][j]); } } @@ -131,13 +131,13 @@ class RemoveBenchmark extends RTreeBenchmarkBase { void setup() { tree = RTree(branchFactor); - for (int i = 0; i < 100; i++) { - for (int j = 0; j < 100; j++) { + for (var i = 0; i < 100; i++) { + for (var j = 0; j < 100; j++) { if (items.length <= i) { items.add([]); } - Rectangle rect = Rectangle(i, j, 1, 1); + final rect = Rectangle(i, j, 1, 1); final datum = RTreeDatum(rect, 'item $i:$j'); items[i].add(datum); tree.insert(datum); @@ -171,12 +171,12 @@ class SearchBenchmark extends RTreeBenchmarkBase { @override void run() { - for (int x = 0; x < size; x++) { - for (int y = 0; y < size; y++) { - var results = tree.search(Rectangle(x, y, 1, 1)); + for (var x = 0; x < size; x++) { + for (var y = 0; y < size; y++) { + final results = tree.search(Rectangle(x, y, 1, 1)); if (iterateAll) { // ignore: unused_local_variable - for (var result in results) { + for (final result in results) { // nothing to do here, just iterating over every result once } } @@ -189,10 +189,10 @@ class SearchBenchmark extends RTreeBenchmarkBase { size = sqrt(totalItems).ceil(); tree = RTree(branchFactor); - var datum = >[]; - for (int i = 0; i < 10; i++) { - for (int j = 0; j < 50; j++) { - Rectangle rect = Rectangle(i, j, 1, 1); + final datum = >[]; + for (var i = 0; i < 10; i++) { + for (var j = 0; j < 50; j++) { + final rect = Rectangle(i, j, 1, 1); datum.add(RTreeDatum(rect, 'item1')); datum.add(RTreeDatum(rect, 'item2')); datum.add(RTreeDatum(rect, 'item3')); @@ -224,7 +224,7 @@ class RTreeBenchmarkBase extends BenchmarkBase { @override void exercise() { - for (int i = 0; i < iterations; i++) { + for (var i = 0; i < iterations; i++) { run(); } } diff --git a/benchmark/web_benchmarks.dart b/benchmark/web_benchmarks.dart index 07f7f6a..9443a94 100644 --- a/benchmark/web_benchmarks.dart +++ b/benchmark/web_benchmarks.dart @@ -2,7 +2,7 @@ import 'dart:html'; import 'benchmarks.dart' as benchmarks; -main() { +void main() { final button = querySelector('#runButton')! as ButtonElement; button.onClick.listen((_) async { button.disabled = true; diff --git a/example/main.dart b/example/main.dart index 1dd7617..6e3d299 100644 --- a/example/main.dart +++ b/example/main.dart @@ -5,15 +5,15 @@ import 'package:r_tree/r_tree.dart'; Future main() async { var rtree = RTree(); - var app = querySelector('#app')!; - var canvas = CanvasElement(width: 640, height: 480); + final app = querySelector('#app')!; + final canvas = CanvasElement(width: 640, height: 480); app.append(canvas); canvas.context2D ..fillStyle = '#ccc' ..fillRect(0, 0, 640, 480); int? startX, startY, proposedX, proposedY; - draw() { + void draw() { canvas.context2D.clearRect(0, 0, 700, 500); canvas.context2D.strokeStyle = ''; rtree.search(Rectangle(0, 0, 700, 500)).forEach((node) { @@ -29,9 +29,9 @@ Future main() async { } var isDrawing = false; - canvas.onMouseDown.listen((MouseEvent event) { - var target = event.currentTarget as HtmlElement; - var boundingRect = target.getBoundingClientRect(); + canvas.onMouseDown.listen((event) { + final target = event.currentTarget! as HtmlElement; + final boundingRect = target.getBoundingClientRect(); isDrawing = true; proposedX = null; proposedY = null; @@ -40,29 +40,29 @@ Future main() async { startY = ((event.client.y - boundingRect.top) + target.scrollTop).floor(); }); - canvas.onMouseMove.listen((MouseEvent event) { + canvas.onMouseMove.listen((event) { if (!isDrawing || startX == null || startY == null) return; - var target = event.currentTarget as HtmlElement; - var boundingRect = target.getBoundingClientRect(); + final target = event.currentTarget! as HtmlElement; + final boundingRect = target.getBoundingClientRect(); proposedX = ((event.client.x - boundingRect.left) + target.scrollLeft).floor(); proposedY = ((event.client.y - boundingRect.top) + target.scrollTop).floor(); draw(); }); - canvas.onMouseUp.listen((MouseEvent event) { + canvas.onMouseUp.listen((event) { isDrawing = false; if (startX == null || startY == null) return; - var target = event.currentTarget as HtmlElement; - var boundingRect = target.getBoundingClientRect(); - var endX = ((event.client.x - boundingRect.left) + target.scrollLeft).floor(); - var endY = ((event.client.y - boundingRect.top) + target.scrollTop).floor(); + final target = event.currentTarget! as HtmlElement; + final boundingRect = target.getBoundingClientRect(); + final endX = ((event.client.x - boundingRect.left) + target.scrollLeft).floor(); + final endY = ((event.client.y - boundingRect.top) + target.scrollTop).floor(); - var rectangle = Rectangle.fromPoints(Point(startX!, startY!), Point(endX, endY)); + final rectangle = Rectangle.fromPoints(Point(startX!, startY!), Point(endX, endY)); if (currentBrush == 'search') { - var resultList = querySelector('#results')!; + final resultList = querySelector('#results')!; resultList.children = []; for (final match in rtree.search(rectangle)) { var color = ''; @@ -96,7 +96,7 @@ Future main() async { final blueButton = querySelector('#blue')!; final searchButton = querySelector('#search')!; final allButtons = [redButton, greenButton, blueButton, searchButton]; - resetAllButtons() { + void resetAllButtons() { for (final element in allButtons) { element.style.background = ''; } @@ -123,16 +123,16 @@ Future main() async { searchButton.style.background = 'darkgray'; }); - makeDataset() { - Random rand = Random(); - var datum = >[]; - for (int i = 0; i < 300; i++) { - int startX = rand.nextInt((canvas.width! / 2).floor()); - int endX = rand.nextInt((canvas.width! / 2).floor()) * 2; - int startY = rand.nextInt((canvas.height! / 2).floor()); - int endY = rand.nextInt((canvas.width! / 2).floor()) * 2; - int color = rand.nextInt(2); - var item = RTreeDatum(Rectangle.fromPoints(Point(startX, startY), Point(endX, endY)), colors[color]); + List> makeDataset() { + final rand = Random(); + final datum = >[]; + for (var i = 0; i < 300; i++) { + final startX = rand.nextInt((canvas.width! / 2).floor()); + final endX = rand.nextInt((canvas.width! / 2).floor()) * 2; + final startY = rand.nextInt((canvas.height! / 2).floor()); + final endY = rand.nextInt((canvas.width! / 2).floor()) * 2; + final color = rand.nextInt(2); + final item = RTreeDatum(Rectangle.fromPoints(Point(startX, startY), Point(endX, endY)), colors[color]); datum.add(item); } return datum; @@ -154,14 +154,14 @@ Future main() async { }); querySelector('#graphviz')!.onClick.listen((_) { - var output = querySelector('#output') as PreElement; + final output = querySelector('#output')! as PreElement; output.innerHtml = toGraphViz(rtree.currentRootNode); }); querySelector('#copy')!.onClick.listen((_) async { try { - await window.navigator.clipboard?.writeText((querySelector('#output') as PreElement).innerText); + await window.navigator.clipboard?.writeText((querySelector('#output')! as PreElement).innerText); querySelector('#copy')!.style.background = 'green'; await Future.delayed(Duration(milliseconds: 350)); querySelector('#copy')!.style.background = ''; @@ -179,7 +179,7 @@ const colors = [red, green, blue]; String currentBrush = red; String toGraphViz(Node root) { - var output = StringBuffer('''digraph r_tree { + final output = StringBuffer('''digraph r_tree { root [ color="gray" label="root" @@ -194,9 +194,9 @@ String toGraphViz(Node root) { void _graphVizRecurse(Node node, String parent, String identifierPrefix, StringBuffer buffer) { for (var i = 0; i < node.children.length; i++) { - var child = node.children[i]; + final child = node.children[i]; if (child is LeafNode) { - var id = "${identifierPrefix}LeafNode$i"; + final id = '${identifierPrefix}LeafNode$i'; buffer.write(''' $id [ color="green" @@ -205,8 +205,8 @@ void _graphVizRecurse(Node node, String parent, String identifierPrefix, StringB $parent -> $id '''); for (var j = 0; j < child.children.length; j++) { - var leafChild = child.children[j]; - var childId = "${id}LeafChild$j"; + final leafChild = child.children[j]; + final childId = '${id}LeafChild$j'; buffer.write(''' "$childId" [ color="orange" @@ -216,7 +216,7 @@ $id -> "$childId" '''); } } else if (child is NonLeafNode) { - var id = "${identifierPrefix}ChildNode$i"; + final id = '${identifierPrefix}ChildNode$i'; buffer.write(''' $id [ color="brown" @@ -226,7 +226,7 @@ $id -> "$childId" '''); _graphVizRecurse(child, id, id, buffer); } else if (child is RTreeDatum) { - var id = "${identifierPrefix}Datum$i"; + final id = '${identifierPrefix}Datum$i'; buffer.write(''' "$id" [ color="orange" diff --git a/lib/src/r_tree/leaf_node.dart b/lib/src/r_tree/leaf_node.dart index 2ee7de6..eed7dd4 100644 --- a/lib/src/r_tree/leaf_node.dart +++ b/lib/src/r_tree/leaf_node.dart @@ -46,8 +46,8 @@ class LeafNode extends Node { @override Iterable> search(Rectangle searchRect, bool Function(E item)? shouldInclude) { - return _items.where( - (RTreeDatum item) => item.rect.overlaps(searchRect) && (shouldInclude == null || shouldInclude(item.value))); + return _items + .where((item) => item.rect.overlaps(searchRect) && (shouldInclude == null || shouldInclude(item.value))); } @override @@ -57,12 +57,12 @@ class LeafNode extends Node { } @override - remove(RTreeDatum item) { + void remove(RTreeDatum item) { removeChild(item); } @override - clearChildren() { + void clearChildren() { _items.clear(); _minimumBoundingRect = noMBR; } diff --git a/lib/src/r_tree/node.dart b/lib/src/r_tree/node.dart index 3fc8417..7c36b74 100644 --- a/lib/src/r_tree/node.dart +++ b/lib/src/r_tree/node.dart @@ -45,10 +45,10 @@ abstract class Node implements RTreeContributor { Node? insert(RTreeDatum item); /// Removes [item] from this node - remove(RTreeDatum item); + void remove(RTreeDatum item); /// Remove all children from this node - clearChildren(); + void clearChildren(); /// Returns a list of all items in this node List get children; @@ -60,13 +60,13 @@ abstract class Node implements RTreeContributor { int get size => children.length; /// Adds [child] to this node - addChild(covariant RTreeContributor child) { + void addChild(covariant RTreeContributor child) { include(child); children.add(child); } /// Removes [child] from this node - removeChild(covariant RTreeContributor child) { + void removeChild(covariant RTreeContributor child) { children.remove(child); updateBoundingRect(); } @@ -78,7 +78,7 @@ abstract class Node implements RTreeContributor { return _area(item.rect); } - Rectangle newRect = rect.boundingBox(item.rect); + final newRect = rect.boundingBox(item.rect); return _area(newRect) - _area(rect); } @@ -87,7 +87,7 @@ abstract class Node implements RTreeContributor { num get margin => (rect.right - rect.left) + (rect.bottom - rect.top); /// Adds the rectangle containing [item] to this node's covered rectangle - include(RTreeContributor item) { + void include(RTreeContributor item) { _minimumBoundingRect = _minimumBoundingRect == noMBR ? item.rect : rect.boundingBox(item.rect); } @@ -113,16 +113,16 @@ abstract class Node implements RTreeContributor { Node? splitIfNecessary() => size > branchFactor ? _split() : null; Node _split() { - _Seeds seeds = _pickSeeds(); + final seeds = _pickSeeds(); removeChild(seeds.seed1); removeChild(seeds.seed2); - List remainingChildren = children.toList(); + final remainingChildren = children.toList(); clearChildren(); addChild(seeds.seed1); - Node splitNode = createNewNode(); + final splitNode = createNewNode(); splitNode.height = height; splitNode.addChild(seeds.seed2); @@ -132,9 +132,9 @@ abstract class Node implements RTreeContributor { } void _reassignRemainingChildren(List remainingChildren, Node splitNode) { - for (var child in remainingChildren) { - num thisExpansionCost = expansionCost(child); - num splitExpansionCost = splitNode.expansionCost(child); + for (final child in remainingChildren) { + final thisExpansionCost = expansionCost(child); + final splitExpansionCost = splitNode.expansionCost(child); if (thisExpansionCost < splitExpansionCost) { this.addChild(child); @@ -152,12 +152,12 @@ abstract class Node implements RTreeContributor { RTreeContributor seed1; RTreeContributor seed2; - RTreeContributor leftmost = children.elementAt(0); - RTreeContributor rightmost = children.elementAt(0); - RTreeContributor topmost = children.elementAt(0); - RTreeContributor bottommost = children.elementAt(0); + var leftmost = children.elementAt(0); + var rightmost = children.elementAt(0); + var topmost = children.elementAt(0); + var bottommost = children.elementAt(0); - for (var child in children) { + for (final child in children) { if (child.rect.right < leftmost.rect.right) leftmost = child; if (child.rect.left > rightmost.rect.left) rightmost = child; if (child.rect.top > bottommost.rect.top) bottommost = child; diff --git a/lib/src/r_tree/non_leaf_node.dart b/lib/src/r_tree/non_leaf_node.dart index 07d3105..3f3f1d2 100644 --- a/lib/src/r_tree/non_leaf_node.dart +++ b/lib/src/r_tree/non_leaf_node.dart @@ -42,9 +42,9 @@ class NonLeafNode extends Node { @override Iterable> search(Rectangle searchRect, bool Function(E item)? shouldInclude) { - List> overlappingLeafs = []; + final overlappingLeafs = >[]; - for (var childNode in _childNodes) { + for (final childNode in _childNodes) { if (childNode.rect.overlaps(searchRect)) { overlappingLeafs.addAll(childNode.search(searchRect, shouldInclude)); } @@ -57,8 +57,8 @@ class NonLeafNode extends Node { Node? insert(RTreeDatum item) { include(item); - Node bestNode = _getBestNodeForInsert(item); - Node? splitNode = bestNode.insert(item); + final bestNode = _getBestNodeForInsert(item); + final splitNode = bestNode.insert(item); if (splitNode != null) { addChild(splitNode); @@ -68,10 +68,10 @@ class NonLeafNode extends Node { } @override - remove(RTreeDatum item) { - List> childrenToRemove = []; + void remove(RTreeDatum item) { + final childrenToRemove = >[]; - for (var childNode in _childNodes) { + for (final childNode in _childNodes) { if (childNode.rect.overlaps(item.rect)) { childNode.remove(item); @@ -81,7 +81,7 @@ class NonLeafNode extends Node { } } - for (var child in childrenToRemove) { + for (final child in childrenToRemove) { removeChild(child); } @@ -89,13 +89,13 @@ class NonLeafNode extends Node { } @override - addChild(Node child) { + void addChild(Node child) { super.addChild(child); child.parent = this; } @override - removeChild(Node child) { + void removeChild(Node child) { super.removeChild(child); child.parent = null; @@ -103,14 +103,14 @@ class NonLeafNode extends Node { } @override - clearChildren() { + void clearChildren() { _childNodes.clear(); _minimumBoundingRect = noMBR; } Node _getBestNodeForInsert(RTreeDatum item) { - Node bestNode = _childNodes[0]; - num bestCost = bestNode.expansionCost(item); + var bestNode = _childNodes[0]; + var bestCost = bestNode.expansionCost(item); for (var i = 1; i < _childNodes.length; i++) { final child = _childNodes[i]; @@ -124,7 +124,7 @@ class NonLeafNode extends Node { return bestNode; } - _updateHeightAndBounds() { + void _updateHeightAndBounds() { var maxChildHeight = 0; for (final childNode in _childNodes) { maxChildHeight = max(maxChildHeight, childNode.height); diff --git a/lib/src/r_tree/quickselect.dart b/lib/src/r_tree/quickselect.dart index 3018104..5d175bf 100644 --- a/lib/src/r_tree/quickselect.dart +++ b/lib/src/r_tree/quickselect.dart @@ -3,7 +3,7 @@ part of r_tree; // sort an array so that items come in groups of n unsorted items, with groups sorted between each other; // combines selection algorithm with binary divide & conquer approach -multiSelect(List arr, int left, int right, int n, int Function(E a, E b) compare) { +void multiSelect(List arr, int left, int right, int n, int Function(E a, E b) compare) { final stack = [left, right]; while (stack.isNotEmpty) { diff --git a/lib/src/r_tree/r_tree.dart b/lib/src/r_tree/r_tree.dart index 68db2f8..1cccd88 100644 --- a/lib/src/r_tree/r_tree.dart +++ b/lib/src/r_tree/r_tree.dart @@ -33,7 +33,7 @@ class RTree { Node get currentRootNode => _root; /// Removes [item] from the rtree - remove(RTreeDatum item) { + void remove(RTreeDatum item) { _root.remove(item); if (_root.children.isEmpty) { @@ -42,7 +42,7 @@ class RTree { } /// Adds [item] to the rtree - insert(RTreeDatum item) { + void insert(RTreeDatum item) { final splitNode = _root.insert(item); if (splitNode != null) { @@ -77,7 +77,7 @@ class RTree { } // recursively build the tree with the given data from scratch using OMT algorithm - Node node = _build(items, 0, items.length - 1, 0); + var node = _build(items, 0, items.length - 1, 0); if (_root.children.isEmpty) { // save as is if tree is empty @@ -101,7 +101,7 @@ class RTree { } void _insertTree(int level, Node inode) { - final List> insertPath = []; + final insertPath = >[]; // find the best node for accommodating the item, saving all nodes along the path too final node = _chooseSubtree(inode, _root, level, insertPath); @@ -121,7 +121,7 @@ class RTree { } // fix all the bounding rectangles along the insertion path - for (var e in insertPath) { + for (final e in insertPath) { e.updateBoundingRect(); } } @@ -199,12 +199,12 @@ class RTree { multiSelect(items, left, right, n1, _compareRectLeft); - for (int i = left; i <= right; i += n1) { + for (var i = left; i <= right; i += n1) { final right2 = min(i + n1 - 1, right); multiSelect(items, i, right2, n2, _compareRectTop); - for (int j = i; j <= right2; j += n2) { + for (var j = i; j <= right2; j += n2) { final right3 = min(j + n2 - 1, right2); // pack each entry recursively @@ -327,7 +327,7 @@ class RTree { final destNode = LeafNode(_branchFactor); destNode._minimumBoundingRect = node.children[0].rect; - for (int i = startChild; i < stopChild; i++) { + for (var i = startChild; i < stopChild; i++) { destNode.extend(node.children[i].rect); } return destNode; @@ -338,7 +338,7 @@ class RTree { } void _growTree(Node node1, Node node2) { - NonLeafNode newRoot = NonLeafNode(_branchFactor, initialChildNodes: [node1, node2]); + final newRoot = NonLeafNode(_branchFactor, initialChildNodes: [node1, node2]); newRoot.height = _root.height + 1; _root = newRoot; node1.parent = _root; diff --git a/pubspec.yaml b/pubspec.yaml index cbecb7e..10912c7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,3 +15,4 @@ dev_dependencies: dependency_validator: ^3.0.0 lints: ^2.0.1 test: ^1.15.7 + workiva_analysis_options: ^1.4.0 diff --git a/test/r_tree/leaf_node_test.dart b/test/r_tree/leaf_node_test.dart index 5b8d7cb..8890c93 100644 --- a/test/r_tree/leaf_node_test.dart +++ b/test/r_tree/leaf_node_test.dart @@ -5,14 +5,14 @@ import 'dart:math'; import 'package:r_tree/r_tree.dart'; import 'package:test/test.dart'; -main() { +void main() { group('LeafNode', () { group('createNewNode', () { test('test that the right type of Node is created', () { - LeafNode leafNode = LeafNode(10); + final leafNode = LeafNode(10); leafNode.addChild(RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 1')); - Node newNode = leafNode.createNewNode(); + final newNode = leafNode.createNewNode(); expect(newNode is LeafNode, equals(true)); expect(newNode.size, equals(0)); expect(newNode.branchFactor, equals(10)); @@ -21,7 +21,7 @@ main() { group('addChild/removeChild', () { test('adding/clearing children updates the rect', () { - LeafNode leaf = LeafNode(3); + final leaf = LeafNode(3); expect(leaf.rect, equals(noMBR)); expect(leaf.size, equals(0)); @@ -31,7 +31,7 @@ main() { expect(leaf.rect, equals(Rectangle(0, 0, 1, 1))); expect(leaf.size, equals(1)); - RTreeDatum nextChild = RTreeDatum(Rectangle(1, 1, 1, 1), 'Item 1'); + final nextChild = RTreeDatum(Rectangle(1, 1, 1, 1), 'Item 1'); leaf.addChild(nextChild); expect(leaf.rect, equals(Rectangle(0, 0, 2, 2))); diff --git a/test/r_tree/node_test.dart b/test/r_tree/node_test.dart index 8a61539..83735f4 100644 --- a/test/r_tree/node_test.dart +++ b/test/r_tree/node_test.dart @@ -5,15 +5,15 @@ import 'dart:math'; import 'package:r_tree/r_tree.dart'; import 'package:test/test.dart'; -main() { +void main() { group('Node', () { group('splitIfNecessary', () { test('split should not occur until branchFactor is exceeded', () { - LeafNode leafNode = LeafNode(10); - Map itemMap = {}; + final leafNode = LeafNode(10); + final itemMap = {}; - for (int i = 0; i < 4; i++) { - String itemId = 'Item $i'; + for (var i = 0; i < 4; i++) { + final itemId = 'Item $i'; itemMap[itemId] = RTreeDatum(Rectangle(0, i, 1, 1), itemId); leafNode.addChild(itemMap[itemId]); } @@ -23,18 +23,18 @@ main() { }); test('test that split correctly splits a column', () { - LeafNode leafNode = LeafNode(3); - Map itemMap = {}; + final leafNode = LeafNode(3); + final itemMap = {}; - for (int i = 0; i < 4; i++) { - String itemId = 'Item $i'; + for (var i = 0; i < 4; i++) { + final itemId = 'Item $i'; itemMap[itemId] = RTreeDatum(Rectangle(0, i, 1, 1), itemId); leafNode.addChild(itemMap[itemId]); } expect(leafNode.size, equals(4)); - LeafNode splitNode = leafNode.splitIfNecessary() as LeafNode; + final splitNode = leafNode.splitIfNecessary()! as LeafNode; Iterable items = leafNode.search(Rectangle(0, 0, 1, 10), (_) => true); expect(items.length, equals(leafNode.size)); @@ -50,18 +50,18 @@ main() { }); test('test that split correctly splits a row', () { - LeafNode leafNode = LeafNode(3); - Map itemMap = {}; + final leafNode = LeafNode(3); + final itemMap = {}; - for (int i = 0; i < 4; i++) { - String itemId = 'Item $i'; + for (var i = 0; i < 4; i++) { + final itemId = 'Item $i'; itemMap[itemId] = RTreeDatum(Rectangle(i, 0, 1, 1), itemId); leafNode.addChild(itemMap[itemId]); } expect(leafNode.size, equals(4)); - LeafNode splitNode = leafNode.splitIfNecessary() as LeafNode; + final splitNode = leafNode.splitIfNecessary()! as LeafNode; Iterable items = leafNode.search(Rectangle(0, 0, 10, 1), (_) => true); expect(items.length, equals(leafNode.size)); @@ -77,18 +77,18 @@ main() { }); test('test that split correctly splits a random cluster', () { - LeafNode leafNode = LeafNode(3); - Map itemMap = {}; + final leafNode = LeafNode(3); + final itemMap = {}; - for (int i = 0; i < 4; i++) { - String itemId = 'Item $i'; + for (var i = 0; i < 4; i++) { + final itemId = 'Item $i'; itemMap[itemId] = RTreeDatum(Rectangle(i, 0, 1, 1), itemId); leafNode.addChild(itemMap[itemId]); } expect(leafNode.size, equals(4)); - LeafNode splitNode = leafNode.splitIfNecessary() as LeafNode; + final splitNode = leafNode.splitIfNecessary()! as LeafNode; Iterable items = leafNode.search(Rectangle(0, 0, 10, 1), (_) => true); expect(items.length, equals(leafNode.size)); @@ -106,7 +106,7 @@ main() { group('expansionCost', () { test('expansionCost correctly calculated', () { - LeafNode node = LeafNode(3); + final node = LeafNode(3); expect(node.expansionCost(RTreeDatum(Rectangle(0, 0, 1, 1), '')), equals(1)); diff --git a/test/r_tree/non_leaf_node_test.dart b/test/r_tree/non_leaf_node_test.dart index b1abe24..2f7bfd8 100644 --- a/test/r_tree/non_leaf_node_test.dart +++ b/test/r_tree/non_leaf_node_test.dart @@ -5,14 +5,14 @@ import 'dart:math'; import 'package:r_tree/r_tree.dart'; import 'package:test/test.dart'; -main() { +void main() { group('NonLeafNode', () { group('createNewNode', () { test('test that the right type of Node is created', () { - NonLeafNode node = NonLeafNode(10); + final node = NonLeafNode(10); node.addChild(LeafNode(10)); - Node newNode = node.createNewNode(); + final newNode = node.createNewNode(); expect(newNode is NonLeafNode, equals(true)); expect(newNode.size, equals(0)); expect(newNode.branchFactor, equals(10)); @@ -21,19 +21,19 @@ main() { group('addChild/removeChild', () { test('adding/clearing children updates the rect', () { - NonLeafNode node = NonLeafNode(3); + final node = NonLeafNode(3); expect(node.rect, equals(noMBR)); expect(node.size, equals(0)); - LeafNode leaf = LeafNode(3); + final leaf = LeafNode(3); leaf.addChild(RTreeDatum(Rectangle(0, 0, 1, 1), '')); node.addChild(leaf); expect(node.rect, equals(Rectangle(0, 0, 1, 1))); expect(node.size, equals(1)); - LeafNode nextChild = LeafNode(3); + final nextChild = LeafNode(3); nextChild.addChild(RTreeDatum(Rectangle(1, 1, 1, 1), '')); node.addChild(nextChild); diff --git a/test/r_tree/r_tree_test.dart b/test/r_tree/r_tree_test.dart index 66c5e7c..2465b21 100644 --- a/test/r_tree/r_tree_test.dart +++ b/test/r_tree/r_tree_test.dart @@ -5,12 +5,12 @@ import 'dart:math'; import 'package:r_tree/r_tree.dart'; import 'package:test/test.dart'; -main() { +void main() { group('RTree', () { group('Insert/Search', () { test('insert 1 item', () { - RTree tree = RTree(3); - RTreeDatum item = RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 1'); + final tree = RTree(3); + final item = RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 1'); tree.insert(item); assertTreeValidity(tree); @@ -33,32 +33,32 @@ main() { items = tree.search(item.rect); expect(items.length, equals(5)); - for (var item in items) { + for (final item in items) { tree.remove(item); } assertTreeValidity(tree); - items = tree.search((item.rect)); + items = tree.search(item.rect); expect(items.isEmpty, isTrue); }); final addMethods = [ - _InsertCase('insert', (RTree tree, Iterable> toAdd) { + _InsertCase('insert', (tree, toAdd) { toAdd.forEach(tree.insert); }), - _InsertCase('load', (RTree tree, Iterable> toAdd) { + _InsertCase('load', (tree, toAdd) { tree.load(toAdd.toList()); }) ]; for (final addMethod in addMethods) { test('search for 1 cell in large format ranges (${addMethod.name})', () { - RTree tree = RTree(3); - Map itemMap = {}; - List> itemsToInsert = []; + final tree = RTree(3); + final itemMap = {}; + final itemsToInsert = >[]; - for (int i = 0; i < 10; i++) { - String itemId = 'Item $i'; + for (var i = 0; i < 10; i++) { + final itemId = 'Item $i'; itemMap[itemId] = RTreeDatum(Rectangle(i, 0, 10 - i, 10), itemId); itemsToInsert.add(itemMap[itemId]); } @@ -84,14 +84,15 @@ main() { }); test('insert enough items to cause split (${addMethod.name})', () { - RTree tree = RTree(3); - Map itemMap = {}; - List> itemsToInsert = []; - - for (int i = 0; i < 5; i++) { - String itemId = 'Item $i'; - itemMap[itemId] = RTreeDatum(Rectangle(0, i, 1, 1), itemId); - itemsToInsert.add(itemMap[itemId]); + final tree = RTree(3); + final itemMap = {}; + final itemsToInsert = >[]; + + for (var i = 0; i < 5; i++) { + final itemId = 'Item $i'; + final item = RTreeDatum(Rectangle(0, i, 1, 1), itemId); + itemMap[itemId] = item; + itemsToInsert.add(item); } addMethod.method(tree, itemsToInsert); @@ -116,12 +117,12 @@ main() { }); test('insert large amount of items (${addMethod.name})', () { - RTree tree = RTree(16); - List> itemsToInsert = []; + final tree = RTree(16); + final itemsToInsert = >[]; - for (int i = 0; i < 50; i++) { - for (int j = 0; j < 50; j++) { - RTreeDatum item = RTreeDatum(Rectangle(i, j, 1, 1), 'Item $i:$j'); + for (var i = 0; i < 50; i++) { + for (var j = 0; j < 50; j++) { + final item = RTreeDatum(Rectangle(i, j, 1, 1), 'Item $i:$j'); itemsToInsert.add(item); } } @@ -141,8 +142,8 @@ main() { group('Remove', () { test('remove should only remove first occurrence of item', () { - RTree tree = RTree(3); - RTreeDatum item = RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 1'); + final tree = RTree(3); + final item = RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 1'); tree.insert(item); tree.insert(item); @@ -171,12 +172,12 @@ main() { }); test('remove from large tree', () { - RTree tree = RTree(16); - Map itemMap = {}; + final tree = RTree(16); + final itemMap = {}; - for (int i = 0; i < 50; i++) { - for (int j = 0; j < 50; j++) { - String itemId = 'Item $i:$j'; + for (var i = 0; i < 50; i++) { + for (var j = 0; j < 50; j++) { + final itemId = 'Item $i:$j'; itemMap[itemId] = RTreeDatum(Rectangle(i, j, 1, 1), itemId); tree.insert(itemMap[itemId]); } @@ -203,12 +204,12 @@ main() { }); test('remove all items from tree', () { - RTree tree = RTree(12); - List data = []; + final tree = RTree(12); + final data = []; - for (int i = 0; i < 50; i++) { - for (int j = 0; j < 50; j++) { - RTreeDatum item = RTreeDatum(Rectangle(i, j, 1, 1), 'Item $i:$j'); + for (var i = 0; i < 50; i++) { + for (var j = 0; j < 50; j++) { + final item = RTreeDatum(Rectangle(i, j, 1, 1), 'Item $i:$j'); data.add(item); tree.insert(item); } @@ -220,7 +221,7 @@ main() { var items = tree.search(Rectangle(0, 0, 50, 50)); expect(items.length, equals(2500)); - for (var item in data) { + for (final item in data) { tree.remove(item); } assertTreeValidity(tree); @@ -236,7 +237,7 @@ main() { items = tree.search(Rectangle(0, 0, 50, 50)); - for (var datum in items) { + for (final datum in items) { expect(datum.value, equals('New Initial Item')); } }); @@ -244,7 +245,7 @@ main() { test('remove all items and then reload', () { final tree = RTree(3); - var items = >[]; + final items = >[]; for (var i = 0; i < 20; i++) { final item = RTreeDatum(Rectangle(0, i, 1, 1), 'Item $i'); items.add(item); @@ -324,7 +325,7 @@ SubtreeValidationData assertNonLeafNodeValidity(RTree tree, NonLeafNode } // Assert parent references for children point back to this node - for (var child in node.children) { + for (final child in node.children) { if (child.parent != node) { throw StateError("Non-leaf child's parent reference is incorrect."); } @@ -338,7 +339,7 @@ SubtreeValidationData assertNonLeafNodeValidity(RTree tree, NonLeafNode final actualRect = getMinimumBoundingRectangle(childrenRects) ?? const Rectangle(0, 0, 0, 0); // Recalculate the actual height for this subtree using validation data - compareMaxWithChild(int maxHeight, SubtreeValidationData child) => max(maxHeight, child.height); + int compareMaxWithChild(int maxHeight, SubtreeValidationData child) => max(maxHeight, child.height); final maxChildHeight = childrenValidationData.fold(0, compareMaxWithChild); // Assert this node's height matches its actual structure From c7baef2a4c8972d3965f473929d23dc8ab5cf85e Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Fri, 10 Nov 2023 12:15:37 -0600 Subject: [PATCH 05/16] Apply lints to new code --- lib/src/r_tree/node.dart | 6 ++---- test/r_tree/r_tree_test.dart | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/src/r_tree/node.dart b/lib/src/r_tree/node.dart index 54542ff..fe77013 100644 --- a/lib/src/r_tree/node.dart +++ b/lib/src/r_tree/node.dart @@ -96,8 +96,7 @@ abstract class Node implements RTreeContributor { /// Recalculated the bounding rectangle of this node Rectangle updateBoundingRect() { if (children.isEmpty) { - _minimumBoundingRect = noMBR; - return _minimumBoundingRect; + return _minimumBoundingRect = noMBR; } var updatedBoundingRect = children[0].rect; @@ -105,8 +104,7 @@ abstract class Node implements RTreeContributor { updatedBoundingRect = updatedBoundingRect.boundingBox(children[i].rect); } - _minimumBoundingRect = updatedBoundingRect; - return _minimumBoundingRect; + return _minimumBoundingRect = updatedBoundingRect; } void extend(Rectangle b) { diff --git a/test/r_tree/r_tree_test.dart b/test/r_tree/r_tree_test.dart index 3f83fbf..5191708 100644 --- a/test/r_tree/r_tree_test.dart +++ b/test/r_tree/r_tree_test.dart @@ -274,7 +274,7 @@ void main() { test('has correct parents after _split', () { final tree = RTree(3); - var items = >[]; + final items = >[]; for (var i = 0; i < 1; i++) { final item = RTreeDatum(Rectangle(0, i, 1, 1), 'Item $i'); items.add(item); @@ -282,7 +282,7 @@ void main() { tree.load(items); assertTreeValidity(tree); - var otherItems = >[]; + final otherItems = >[]; for (var i = 0; i < 20; i++) { final item = RTreeDatum(Rectangle(i + 10, 0, 1, 1), 'Item $i'); otherItems.add(item); @@ -305,7 +305,6 @@ void main() { 'Item $index', ), ); - ; tree.load(items); assertTreeValidity(tree); From 42d21b751472995bc309fb255ef541fb10643a73 Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Fri, 10 Nov 2023 12:26:20 -0600 Subject: [PATCH 06/16] Rewrite updateBoundingRect for readability --- lib/src/r_tree/node.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/src/r_tree/node.dart b/lib/src/r_tree/node.dart index fe77013..c396fc1 100644 --- a/lib/src/r_tree/node.dart +++ b/lib/src/r_tree/node.dart @@ -95,16 +95,17 @@ abstract class Node implements RTreeContributor { /// Recalculated the bounding rectangle of this node Rectangle updateBoundingRect() { + _minimumBoundingRect = noMBR; if (children.isEmpty) { - return _minimumBoundingRect = noMBR; + return _minimumBoundingRect; } - var updatedBoundingRect = children[0].rect; + _minimumBoundingRect = children[0].rect; for (var i = 1; i < children.length; i++) { - updatedBoundingRect = updatedBoundingRect.boundingBox(children[i].rect); + _minimumBoundingRect = _minimumBoundingRect.boundingBox(children[i].rect); } - return _minimumBoundingRect = updatedBoundingRect; + return _minimumBoundingRect; } void extend(Rectangle b) { From 69fcc4947a32e444905651ac284c487b354911b6 Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Fri, 10 Nov 2023 12:42:13 -0600 Subject: [PATCH 07/16] Disable implicit casts --- analysis_options.yaml | 8 ++++---- lib/src/r_tree/quickselect.dart | 2 +- lib/src/r_tree/r_tree.dart | 4 ++-- test/r_tree/node_test.dart | 20 ++++++++++++-------- test/r_tree/r_tree_test.dart | 26 ++++++++++++++------------ 5 files changed, 33 insertions(+), 27 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 803fc6a..9b0ab1a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,5 +1,5 @@ include: package:workiva_analysis_options/v2.recommended.yaml -#analyzer: -# strong-mode: -# implicit-casts: false -# implicit-dynamic: true +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: true diff --git a/lib/src/r_tree/quickselect.dart b/lib/src/r_tree/quickselect.dart index 1771ee9..dd1dfec 100644 --- a/lib/src/r_tree/quickselect.dart +++ b/lib/src/r_tree/quickselect.dart @@ -108,7 +108,7 @@ void _quickSelectStep(List arr, int k, int left, int right, Comparator } } -void _swap(List arr, i, j) { +void _swap(List arr, int i, int j) { final tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; diff --git a/lib/src/r_tree/r_tree.dart b/lib/src/r_tree/r_tree.dart index 839d2b6..eb2bf5b 100644 --- a/lib/src/r_tree/r_tree.dart +++ b/lib/src/r_tree/r_tree.dart @@ -265,7 +265,7 @@ class RTree { _root.height = node.height + 1; } - int _chooseSplitIndex(Node node, m, M) { + int _chooseSplitIndex(Node node, int m, int M) { int? index; num minOverlap = double.infinity; num minArea = double.infinity; @@ -296,7 +296,7 @@ class RTree { return index ?? M - m; } - void _chooseSplitAxis(node, m, M) { + void _chooseSplitAxis(Node node, int m, int M) { final xMargin = _allDistributionMargins(node, m, M, true); final yMargin = _allDistributionMargins(node, m, M, false); diff --git a/test/r_tree/node_test.dart b/test/r_tree/node_test.dart index 83735f4..54c6a91 100644 --- a/test/r_tree/node_test.dart +++ b/test/r_tree/node_test.dart @@ -14,8 +14,9 @@ void main() { for (var i = 0; i < 4; i++) { final itemId = 'Item $i'; - itemMap[itemId] = RTreeDatum(Rectangle(0, i, 1, 1), itemId); - leafNode.addChild(itemMap[itemId]); + final item = RTreeDatum(Rectangle(0, i, 1, 1), itemId); + itemMap[itemId] = item; + leafNode.addChild(item); } expect(leafNode.size, equals(4)); @@ -28,8 +29,9 @@ void main() { for (var i = 0; i < 4; i++) { final itemId = 'Item $i'; - itemMap[itemId] = RTreeDatum(Rectangle(0, i, 1, 1), itemId); - leafNode.addChild(itemMap[itemId]); + final item = RTreeDatum(Rectangle(0, i, 1, 1), itemId); + itemMap[itemId] = item; + leafNode.addChild(item); } expect(leafNode.size, equals(4)); @@ -55,8 +57,9 @@ void main() { for (var i = 0; i < 4; i++) { final itemId = 'Item $i'; - itemMap[itemId] = RTreeDatum(Rectangle(i, 0, 1, 1), itemId); - leafNode.addChild(itemMap[itemId]); + final item = RTreeDatum(Rectangle(i, 0, 1, 1), itemId); + itemMap[itemId] = item; + leafNode.addChild(item); } expect(leafNode.size, equals(4)); @@ -82,8 +85,9 @@ void main() { for (var i = 0; i < 4; i++) { final itemId = 'Item $i'; - itemMap[itemId] = RTreeDatum(Rectangle(i, 0, 1, 1), itemId); - leafNode.addChild(itemMap[itemId]); + final item = RTreeDatum(Rectangle(i, 0, 1, 1), itemId); + itemMap[itemId] = item; + leafNode.addChild(item); } expect(leafNode.size, equals(4)); diff --git a/test/r_tree/r_tree_test.dart b/test/r_tree/r_tree_test.dart index 5191708..0c37a68 100644 --- a/test/r_tree/r_tree_test.dart +++ b/test/r_tree/r_tree_test.dart @@ -59,8 +59,9 @@ void main() { for (var i = 0; i < 10; i++) { final itemId = 'Item $i'; - itemMap[itemId] = RTreeDatum(Rectangle(i, 0, 10 - i, 10), itemId); - itemsToInsert.add(itemMap[itemId]); + final item = RTreeDatum(Rectangle(i, 0, 10 - i, 10), itemId); + itemMap[itemId] = item; + itemsToInsert.add(item); } addMethod.method(tree, itemsToInsert); @@ -172,34 +173,35 @@ void main() { }); test('remove from large tree', () { - final tree = RTree(16); - final itemMap = {}; + final tree = RTree(16); + final itemMap = >{}; for (var i = 0; i < 50; i++) { for (var j = 0; j < 50; j++) { final itemId = 'Item $i:$j'; - itemMap[itemId] = RTreeDatum(Rectangle(i, j, 1, 1), itemId); - tree.insert(itemMap[itemId]); + final item = RTreeDatum(Rectangle(i, j, 1, 1), itemId); + itemMap[itemId] = item; + tree.insert(item); } } assertTreeValidity(tree); - var items = tree.search(itemMap['Item 0:0'].rect); + var items = tree.search(itemMap['Item 0:0']!.rect); expect(items.length, equals(1)); - tree.remove(itemMap['Item 0:0']); + tree.remove(itemMap['Item 0:0']!); assertTreeValidity(tree); - items = tree.search(itemMap['Item 0:0'].rect); + items = tree.search(itemMap['Item 0:0']!.rect); expect(items.length, equals(0)); - items = tree.search(itemMap['Item 13:41'].rect); + items = tree.search(itemMap['Item 13:41']!.rect); expect(items.length, equals(1)); - tree.remove(itemMap['Item 13:41']); + tree.remove(itemMap['Item 13:41']!); assertTreeValidity(tree); - items = tree.search(itemMap['Item 13:41'].rect); + items = tree.search(itemMap['Item 13:41']!.rect); expect(items.length, equals(0)); }); From d004538b96a2cec4e12afaa92183493402d4cb1f Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Mon, 13 Nov 2023 16:58:11 -0600 Subject: [PATCH 08/16] Naive removal of deprecated methods --- benchmark/benchmarks.dart | 10 +- example/main.dart | 6 +- lib/src/r_tree/leaf_node.dart | 2 - lib/src/r_tree/node.dart | 4 +- lib/src/r_tree/non_leaf_node.dart | 3 - lib/src/r_tree/quickselect.dart | 8 +- lib/src/r_tree/r_tree.dart | 12 +- test/r_tree/r_tree_test.dart | 213 ++++++++++++++---------------- 8 files changed, 115 insertions(+), 143 deletions(-) diff --git a/benchmark/benchmarks.dart b/benchmark/benchmarks.dart index e6fcb10..c4848f6 100644 --- a/benchmark/benchmarks.dart +++ b/benchmark/benchmarks.dart @@ -56,7 +56,7 @@ class InsertBenchmark extends RTreeBenchmarkBase { void run() { tree = RTree(branchFactor); for (var data in datum) { - tree.insert(data); + tree.add([data]); } } @@ -86,7 +86,7 @@ class LoadBenchmark extends RTreeBenchmarkBase { void run() { tree = RTree(branchFactor); - tree.load(datum); + tree.add(datum); } void setup() { @@ -132,7 +132,7 @@ class RemoveBenchmark extends RTreeBenchmarkBase { Rectangle rect = Rectangle(i, j, 1, 1); final datum = RTreeDatum(rect, 'item $i:$j'); items[i].add(datum); - tree.insert(datum); + tree.add([datum]); } } } @@ -196,9 +196,9 @@ class SearchBenchmark extends RTreeBenchmarkBase { } if (useLoad) { - tree.load(datum); + tree.add(datum); } else { - datum.forEach(tree.insert); + datum.forEach((item) => tree.add([item])); } } diff --git a/example/main.dart b/example/main.dart index a87f63b..3d78623 100644 --- a/example/main.dart +++ b/example/main.dart @@ -89,7 +89,7 @@ Future main() async { resultList.append(LIElement()..innerHtml = 'No results in $rectangle'); } } else { - rtree.insert(RTreeDatum(rectangle, currentBrush)); + rtree.add([RTreeDatum(rectangle, currentBrush)]); } draw(); @@ -140,12 +140,12 @@ Future main() async { }; querySelector('#insert')!.onClick.listen((_) { - makeDataset().forEach(rtree.insert); + makeDataset().forEach((item) => rtree.add([item])); draw(); }); querySelector('#load')!.onClick.listen((_) { - rtree.load(makeDataset()); + rtree.add(makeDataset()); draw(); }); diff --git a/lib/src/r_tree/leaf_node.dart b/lib/src/r_tree/leaf_node.dart index a735eba..67495bf 100644 --- a/lib/src/r_tree/leaf_node.dart +++ b/lib/src/r_tree/leaf_node.dart @@ -22,7 +22,6 @@ import 'package:r_tree/src/r_tree/rectangle_helper.dart'; /// A [Node] that is a leaf node of the tree. These are created automatically /// by [RTree] when inserting/removing items from the tree. -@Deprecated('For internal use only, removed in next major release') class LeafNode extends Node { final List> _items = []; List> get children => _items; @@ -60,6 +59,5 @@ class LeafNode extends Node { clearChildren() { super.clearChildren(); _items.clear(); - _minimumBoundingRect = noMBR; } } diff --git a/lib/src/r_tree/node.dart b/lib/src/r_tree/node.dart index 6763c03..47941ca 100644 --- a/lib/src/r_tree/node.dart +++ b/lib/src/r_tree/node.dart @@ -20,12 +20,10 @@ import 'package:r_tree/src/r_tree/r_tree_contributor.dart'; import 'package:r_tree/src/r_tree/r_tree_datum.dart'; import 'package:r_tree/src/r_tree/rectangle_helper.dart'; -@Deprecated('For internal use only, removed in next major release') const noMBR = Rectangle(0, 0, 0, 0); /// A [Node] is an entry in the [RTree] for a particular rectangle. This is an /// abstract class, see [LeafNode] and [NonLeafNode] for more information. -@Deprecated('For internal use only, removed in next major release') abstract class Node implements RTreeContributor { /// The branch factor this node is configured with, which determines when the node should split final int branchFactor; @@ -56,7 +54,7 @@ abstract class Node implements RTreeContributor { /// Remove all children from this node clearChildren() { - _minimumBoundingRect = null; + _minimumBoundingRect = noMBR; } /// Returns a list of all items in this node diff --git a/lib/src/r_tree/non_leaf_node.dart b/lib/src/r_tree/non_leaf_node.dart index 4ad0595..f607218 100644 --- a/lib/src/r_tree/non_leaf_node.dart +++ b/lib/src/r_tree/non_leaf_node.dart @@ -16,14 +16,12 @@ import 'dart:math'; -import 'package:r_tree/src/r_tree/leaf_node.dart'; import 'package:r_tree/src/r_tree/node.dart'; import 'package:r_tree/src/r_tree/r_tree_datum.dart'; import 'package:r_tree/src/r_tree/rectangle_helper.dart'; /// A [Node] that is not a leaf end of the [RTree]. These are created automatically /// by [RTree] when inserting/removing items from the tree. -@Deprecated('For internal use only, removed in next major release') class NonLeafNode extends Node { final List> _childNodes = []; List> get children => _childNodes; @@ -102,7 +100,6 @@ class NonLeafNode extends Node { clearChildren() { super.clearChildren(); _childNodes.clear(); - _minimumBoundingRect = noMBR; } Node _getBestNodeForInsert(RTreeDatum item) { diff --git a/lib/src/r_tree/quickselect.dart b/lib/src/r_tree/quickselect.dart index 58c578a..67d1004 100644 --- a/lib/src/r_tree/quickselect.dart +++ b/lib/src/r_tree/quickselect.dart @@ -1,9 +1,8 @@ import 'dart:math'; -// Port of https://github.com/mourner/quickselect. - -// sort an array so that items come in groups of n unsorted items, with groups sorted between each other; -// combines selection algorithm with binary divide & conquer approach +/// Port of https://github.com/mourner/quickselect. +/// sort an array so that items come in groups of n unsorted items, with groups sorted between each other; +/// combines selection algorithm with binary divide & conquer approach multiSelect(List arr, int left, int right, int n, int Function(E a, E b) compare) { final stack = [left, right]; @@ -44,7 +43,6 @@ multiSelect(List arr, int left, int right, int n, int Function(E a, E b) c /// // arr is [39, 28, 28, 33, 21, 12, 22, 50, 53, 56, 59, 65, 90, 77, 95] /// // ^^ middle index /// ``` -@Deprecated('For internal use only, removed in next major release') void quickSelect(List arr, int k, int left, int right, Comparator compare) { if (arr.isEmpty) { return; diff --git a/lib/src/r_tree/r_tree.dart b/lib/src/r_tree/r_tree.dart index d966715..c03d740 100644 --- a/lib/src/r_tree/r_tree.dart +++ b/lib/src/r_tree/r_tree.dart @@ -40,11 +40,11 @@ class RTree { /// Adds all [items] to the rtree void add(List> items) { if (items.length == 1) { - insert(items.first); + _insert(items.first); return; } - load(items); + _load(items); } /// Removes [item] from the rtree @@ -57,8 +57,7 @@ class RTree { } /// Adds [item] to the rtree - @Deprecated('Use add') - insert(RTreeDatum item) { + void _insert(RTreeDatum item) { final splitNode = _root.insert(item); if (splitNode != null) { @@ -80,15 +79,14 @@ class RTree { /// Bulk adds all [items] to the rtree. This implementation draws heavily from /// https://github.com/mourner/rbush and https://github.com/Zverik/dart_rbush. - @Deprecated('Use add') - void load(List> items) { + void _load(List> items) { if (items.isEmpty) { return; } if (items.length < _minEntries) { for (final item in items) { - insert(item); + _insert(item); } return; } diff --git a/test/r_tree/r_tree_test.dart b/test/r_tree/r_tree_test.dart index b85dccf..7438846 100644 --- a/test/r_tree/r_tree_test.dart +++ b/test/r_tree/r_tree_test.dart @@ -6,6 +6,7 @@ import 'package:r_tree/r_tree.dart'; import 'package:r_tree/src/r_tree/leaf_node.dart'; import 'package:r_tree/src/r_tree/node.dart'; import 'package:r_tree/src/r_tree/non_leaf_node.dart'; +import 'package:r_tree/src/r_tree/r_tree.dart'; import 'package:test/test.dart'; main() { @@ -15,7 +16,7 @@ main() { RTree tree = RTree(3); RTreeDatum item = RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 1'); - tree.insert(item); + tree.add([item]); assertTreeValidity(tree); var items = tree.search(item.rect, shouldInclude: (_) => false); @@ -26,10 +27,10 @@ main() { expect(items.elementAt(0).value, equals('Item 1')); items.forEach((item) { - tree.insert(RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 2')); - tree.insert(RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 3')); - tree.insert(RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 4')); - tree.insert(RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 5')); + tree.add([RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 2')]); + tree.add([RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 3')]); + tree.add([RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 4')]); + tree.add([RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 5')]); }); assertTreeValidity(tree); @@ -45,101 +46,90 @@ main() { expect(items.isEmpty, isTrue); }); - final addMethods = [ - _InsertCase('insert', (RTree tree, Iterable> toAdd) { - toAdd.forEach(tree.insert); - }), - _InsertCase('load', (RTree tree, Iterable> toAdd) { - tree.load(toAdd.toList()); - }) - ]; - - for (final addMethod in addMethods) { - test('search for 1 cell in large format ranges (${addMethod.name})', () { - RTree tree = RTree(3); - Map itemMap = Map(); - List> itemsToInsert = []; - - for (int i = 0; i < 10; i++) { - String itemId = 'Item $i'; - itemMap[itemId] = RTreeDatum(Rectangle(i, 0, 10 - i, 10), itemId); - itemsToInsert.add(itemMap[itemId]); - } + test('search for 1 cell in large format ranges ', () { + RTree tree = RTree(3); + Map itemMap = Map(); + List> itemsToInsert = []; - addMethod.method(tree, itemsToInsert); - assertTreeValidity(tree); + for (int i = 0; i < 10; i++) { + String itemId = 'Item $i'; + itemMap[itemId] = RTreeDatum(Rectangle(i, 0, 10 - i, 10), itemId); + itemsToInsert.add(itemMap[itemId]); + } - var items = tree.search(Rectangle(0, 0, 1, 3)); // A1:A3 - expect(items.length, equals(1)); - expect(items.contains(itemMap['Item 0']), equals(true)); + tree.add(itemsToInsert); + assertTreeValidity(tree); - items = tree.search(Rectangle(0, 3, 1, 10)); // A3:A13 - expect(items.length, equals(1)); - expect(items.contains(itemMap['Item 0']), equals(true)); + var items = tree.search(Rectangle(0, 0, 1, 3)); // A1:A3 + expect(items.length, equals(1)); + expect(items.contains(itemMap['Item 0']), equals(true)); - items = tree.search(Rectangle(4, 4, 1, 1)); // E5 - expect(items.length, equals(5)); - expect(items.contains(itemMap['Item 0']), equals(true)); - expect(items.contains(itemMap['Item 1']), equals(true)); - expect(items.contains(itemMap['Item 2']), equals(true)); - expect(items.contains(itemMap['Item 3']), equals(true)); - expect(items.contains(itemMap['Item 4']), equals(true)); - }); + items = tree.search(Rectangle(0, 3, 1, 10)); // A3:A13 + expect(items.length, equals(1)); + expect(items.contains(itemMap['Item 0']), equals(true)); + + items = tree.search(Rectangle(4, 4, 1, 1)); // E5 + expect(items.length, equals(5)); + expect(items.contains(itemMap['Item 0']), equals(true)); + expect(items.contains(itemMap['Item 1']), equals(true)); + expect(items.contains(itemMap['Item 2']), equals(true)); + expect(items.contains(itemMap['Item 3']), equals(true)); + expect(items.contains(itemMap['Item 4']), equals(true)); + }); - test('insert enough items to cause split (${addMethod.name})', () { - RTree tree = RTree(3); - Map itemMap = Map(); - List> itemsToInsert = []; + test('insert enough items to cause split', () { + RTree tree = RTree(3); + Map itemMap = Map(); + List> itemsToInsert = []; - for (int i = 0; i < 5; i++) { - String itemId = 'Item $i'; - itemMap[itemId] = RTreeDatum(Rectangle(0, i, 1, 1), itemId); - itemsToInsert.add(itemMap[itemId]); - } + for (int i = 0; i < 5; i++) { + String itemId = 'Item $i'; + itemMap[itemId] = RTreeDatum(Rectangle(0, i, 1, 1), itemId); + itemsToInsert.add(itemMap[itemId]); + } - addMethod.method(tree, itemsToInsert); - assertTreeValidity(tree); - - var items = tree.search(Rectangle(0, 2, 1, 1)); - expect(items.length, equals(1)); - expect(items.contains(itemMap['Item 2']), equals(true)); - - items = tree.search(Rectangle(0, 1, 1, 2)); - expect(items.length, equals(2)); - expect(items.contains(itemMap['Item 1']), equals(true)); - expect(items.contains(itemMap['Item 2']), equals(true)); - - items = tree.search(Rectangle(0, 0, 1, 5)); - expect(items.length, equals(5)); - expect(items.contains(itemMap['Item 0']), equals(true)); - expect(items.contains(itemMap['Item 1']), equals(true)); - expect(items.contains(itemMap['Item 2']), equals(true)); - expect(items.contains(itemMap['Item 3']), equals(true)); - expect(items.contains(itemMap['Item 4']), equals(true)); - }); + tree.add(itemsToInsert); + assertTreeValidity(tree); - test('insert large amount of items (${addMethod.name})', () { - RTree tree = RTree(16); - List> itemsToInsert = []; + var items = tree.search(Rectangle(0, 2, 1, 1)); + expect(items.length, equals(1)); + expect(items.contains(itemMap['Item 2']), equals(true)); - for (int i = 0; i < 50; i++) { - for (int j = 0; j < 50; j++) { - RTreeDatum item = RTreeDatum(Rectangle(i, j, 1, 1), 'Item $i:$j'); - itemsToInsert.add(item); - } + items = tree.search(Rectangle(0, 1, 1, 2)); + expect(items.length, equals(2)); + expect(items.contains(itemMap['Item 1']), equals(true)); + expect(items.contains(itemMap['Item 2']), equals(true)); + + items = tree.search(Rectangle(0, 0, 1, 5)); + expect(items.length, equals(5)); + expect(items.contains(itemMap['Item 0']), equals(true)); + expect(items.contains(itemMap['Item 1']), equals(true)); + expect(items.contains(itemMap['Item 2']), equals(true)); + expect(items.contains(itemMap['Item 3']), equals(true)); + expect(items.contains(itemMap['Item 4']), equals(true)); + }); + + test('insert large amount of items', () { + RTree tree = RTree(16); + List> itemsToInsert = []; + + for (int i = 0; i < 50; i++) { + for (int j = 0; j < 50; j++) { + RTreeDatum item = RTreeDatum(Rectangle(i, j, 1, 1), 'Item $i:$j'); + itemsToInsert.add(item); } + } - addMethod.method(tree, itemsToInsert); - assertTreeValidity(tree); + tree.add(itemsToInsert); + assertTreeValidity(tree); - var items = tree.search(Rectangle(31, 27, 1, 1)); - expect(items.length, equals(1)); - expect(items.elementAt(0).value, equals('Item 31:27')); + var items = tree.search(Rectangle(31, 27, 1, 1)); + expect(items.length, equals(1)); + expect(items.elementAt(0).value, equals('Item 31:27')); - items = tree.search(Rectangle(0, 0, 2, 50)); - expect(items.length, equals(100)); - }); - } + items = tree.search(Rectangle(0, 0, 2, 50)); + expect(items.length, equals(100)); + }); }); group('Remove', () { @@ -147,8 +137,8 @@ main() { RTree tree = RTree(3); RTreeDatum item = RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 1'); - tree.insert(item); - tree.insert(item); + tree.add([item]); + tree.add([item]); assertTreeValidity(tree); var items = tree.search(item.rect); @@ -166,7 +156,7 @@ main() { items = tree.search(item.rect); expect(items.length, equals(0)); - tree.insert(item); + tree.add([item]); assertTreeValidity(tree); items = tree.search(item.rect); @@ -181,7 +171,7 @@ main() { for (int j = 0; j < 50; j++) { String itemId = 'Item $i:$j'; itemMap[itemId] = RTreeDatum(Rectangle(i, j, 1, 1), itemId); - tree.insert(itemMap[itemId]); + tree.add([itemMap[itemId]]); } } assertTreeValidity(tree); @@ -213,12 +203,12 @@ main() { for (int j = 0; j < 50; j++) { RTreeDatum item = RTreeDatum(Rectangle(i, j, 1, 1), 'Item $i:$j'); data.add(item); - tree.insert(item); + tree.add([item]); } } assertTreeValidity(tree); - expect(tree.currentRootNode, isA>()); + expect(getCurrentRootNode(tree), isA>()); var items = tree.search(Rectangle(0, 0, 50, 50)); expect(items.length, equals(2500)); @@ -231,10 +221,10 @@ main() { items = tree.search(Rectangle(0, 0, 50, 50)); expect(items.length, equals(0)); - expect(tree.currentRootNode, isA>()); + expect(getCurrentRootNode(tree), isA>()); //test inserting after removal to ensure new root leaf node functions correctly - tree.insert(RTreeDatum(Rectangle(0, 0, 1, 1), 'New Initial Item')); + tree.add([RTreeDatum(Rectangle(0, 0, 1, 1), 'New Initial Item')]); assertTreeValidity(tree); items = tree.search(Rectangle(0, 0, 50, 50)); @@ -251,7 +241,7 @@ main() { for (var i = 0; i < 20; i++) { final item = RTreeDatum(Rectangle(0, i, 1, 1), 'Item $i'); items.add(item); - tree.insert(item); + tree.add([item]); } assertTreeValidity(tree); @@ -266,7 +256,7 @@ main() { searchResult = tree.search(Rectangle(0, 0, 1, 20)); expect(searchResult, isEmpty); - tree.load(items.sublist(0, 3)); + tree.add(items.sublist(0, 3)); assertTreeValidity(tree); searchResult = tree.search(Rectangle(0, 0, 1, 20)); @@ -281,7 +271,7 @@ main() { final item = RTreeDatum(Rectangle(0, i, 1, 1), 'Item $i'); items.add(item); } - tree.load(items); + tree.add(items); assertTreeValidity(tree); var otherItems = >[]; @@ -289,7 +279,7 @@ main() { final item = RTreeDatum(Rectangle(i + 10, 0, 1, 1), 'Item $i'); otherItems.add(item); } - tree.load(otherItems); + tree.add(otherItems); assertTreeValidity(tree); }); @@ -297,7 +287,7 @@ main() { final tree = RTree(3); var items = >[RTreeDatum(Rectangle(0, 0, 1, 1), 'Item 0')]; - tree.load(items); + tree.add(items); assertTreeValidity(tree); items = List>.generate( @@ -308,7 +298,7 @@ main() { ), ); ; - tree.load(items); + tree.add(items); assertTreeValidity(tree); items = List>.generate( @@ -318,7 +308,7 @@ main() { 'Item $index', ), ); - tree.load(items); + tree.add(items); expect(tree.search(Rectangle(0, 0, 50, 50)), hasLength(24)); assertTreeValidity(tree); }); @@ -332,7 +322,7 @@ main() { 'Item 0', ) ]; - tree.load(items); + tree.add(items); items = List>.generate( 20, @@ -341,7 +331,7 @@ main() { 'Item $i', ), ); - tree.load(items); + tree.add(items); items = List>.generate( 30, @@ -350,7 +340,7 @@ main() { 'Item $i', ), ); - tree.load(items); + tree.add(items); items = List>.generate( 3, @@ -359,7 +349,7 @@ main() { 'Item $i', ), ); - tree.load(items); + tree.add(items); // the test is a bit convoluted but the key here is the search rectangle // intersects what the subtree's rectangle should be but not what it is @@ -375,7 +365,7 @@ main() { /// rectangles. void assertTreeValidity(RTree tree) { try { - assertNodeValidity(tree, tree.currentRootNode); + assertNodeValidity(tree, getCurrentRootNode(tree)); } on StateError catch (e) { fail('${e.message}\nTree:\n${stringifyTree(tree)}'); } @@ -463,7 +453,7 @@ class _SubtreeValidationData { /// Serializes the tree in a human-readable form for debugging. String stringifyTree(RTree tree) { final buffer = StringBuffer(); - stringifyNode(buffer, tree.currentRootNode, 0); + stringifyNode(buffer, getCurrentRootNode(tree), 0); return buffer.toString(); } @@ -480,13 +470,6 @@ void stringifyNode(StringBuffer buffer, RTreeContributor contributor, int lev } } -class _InsertCase { - final Function(RTree tree, Iterable> toAdd) method; - final String name; - - _InsertCase(this.name, this.method); -} - /// Compute the minimum bounding rectangles of the specified rectangles. Returns null if no rectangles provided. Rectangle? getMinimumBoundingRectangle(Iterable> rectangles) { if (rectangles.isEmpty) { From b697b42a5cfb05ee4242bb53b7de518cef2bec1e Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Mon, 13 Nov 2023 17:10:11 -0600 Subject: [PATCH 09/16] Remove unused dependency --- pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index cdcb12a..7d2aa49 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,5 @@ dev_dependencies: build_web_compilers: ^3.2.7 dart_style: ^2.2.4 dependency_validator: ^3.0.0 - lints: ^2.0.1 test: ^1.15.7 workiva_analysis_options: ^1.4.0 From 99e13927a7c31e513b115ca6808ecdeb119e6d3d Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Mon, 13 Nov 2023 17:16:26 -0600 Subject: [PATCH 10/16] Remove unnecessary this caught in Dart 2.18.7 --- lib/src/r_tree/node.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/r_tree/node.dart b/lib/src/r_tree/node.dart index 492bc75..50b9702 100644 --- a/lib/src/r_tree/node.dart +++ b/lib/src/r_tree/node.dart @@ -146,11 +146,11 @@ abstract class Node implements RTreeContributor { final splitExpansionCost = splitNode.expansionCost(child); if (thisExpansionCost < splitExpansionCost) { - this.addChild(child); + addChild(child); } else if (splitExpansionCost < thisExpansionCost) { splitNode.addChild(child); } else if (size < splitNode.size) { - this.addChild(child); + addChild(child); } else { splitNode.addChild(child); } From fe704f83ed729c1e05fb32cf4c54c0c0d243a11a Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Mon, 13 Nov 2023 17:55:56 -0600 Subject: [PATCH 11/16] Introduce an unnecessary this to test analyze --- lib/src/r_tree/r_tree.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/r_tree/r_tree.dart b/lib/src/r_tree/r_tree.dart index 3260128..6095d7d 100644 --- a/lib/src/r_tree/r_tree.dart +++ b/lib/src/r_tree/r_tree.dart @@ -58,7 +58,7 @@ class RTree { /// Adds [item] to the rtree void _insert(RTreeDatum item) { - final splitNode = _root.insert(item); + final splitNode = this._root.insert(item); if (splitNode != null) { _growTree(_root, splitNode); From 975a1cc999cd440198fd0cb7af7514e7c5f374cd Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Mon, 13 Nov 2023 17:56:14 -0600 Subject: [PATCH 12/16] Try a Dart 3 CI --- .github/workflows/dart_ci.yaml | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dart_ci.yaml b/.github/workflows/dart_ci.yaml index 7c6ddc3..7dca3fb 100644 --- a/.github/workflows/dart_ci.yaml +++ b/.github/workflows/dart_ci.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - sdk: [ 2.18.7, 2.19.2 ] + sdk: [ 2.18.7, 2.19.2, 3.1.5 ] steps: - uses: actions/checkout@v2 - uses: dart-lang/setup-dart@v1 diff --git a/pubspec.yaml b/pubspec.yaml index 7d2aa49..a0629e1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ description: R-tree implementation to index and query two-dimensional data homepage: https://github.com/Workiva/r_tree environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.12.0 <4.0.0' dev_dependencies: benchmark_harness: any From 2d56f4cbe880f2bd324e3b58e460d4a9020fdab5 Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Mon, 13 Nov 2023 17:57:50 -0600 Subject: [PATCH 13/16] Temporarily remove implicit rules --- analysis_options.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 9b0ab1a..798d2c7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,5 +1 @@ include: package:workiva_analysis_options/v2.recommended.yaml -analyzer: - strong-mode: - implicit-casts: false - implicit-dynamic: true From 9787fd141c6bd9ce67b743df2ae77505460df420 Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Mon, 13 Nov 2023 18:01:23 -0600 Subject: [PATCH 14/16] Revert temporary changes --- .github/workflows/dart_ci.yaml | 2 +- analysis_options.yaml | 4 ++++ lib/src/r_tree/r_tree.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dart_ci.yaml b/.github/workflows/dart_ci.yaml index 7dca3fb..7c6ddc3 100644 --- a/.github/workflows/dart_ci.yaml +++ b/.github/workflows/dart_ci.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - sdk: [ 2.18.7, 2.19.2, 3.1.5 ] + sdk: [ 2.18.7, 2.19.2 ] steps: - uses: actions/checkout@v2 - uses: dart-lang/setup-dart@v1 diff --git a/analysis_options.yaml b/analysis_options.yaml index 798d2c7..9b0ab1a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1,5 @@ include: package:workiva_analysis_options/v2.recommended.yaml +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: true diff --git a/lib/src/r_tree/r_tree.dart b/lib/src/r_tree/r_tree.dart index 6095d7d..3260128 100644 --- a/lib/src/r_tree/r_tree.dart +++ b/lib/src/r_tree/r_tree.dart @@ -58,7 +58,7 @@ class RTree { /// Adds [item] to the rtree void _insert(RTreeDatum item) { - final splitNode = this._root.insert(item); + final splitNode = _root.insert(item); if (splitNode != null) { _growTree(_root, splitNode); diff --git a/pubspec.yaml b/pubspec.yaml index a0629e1..7d2aa49 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ description: R-tree implementation to index and query two-dimensional data homepage: https://github.com/Workiva/r_tree environment: - sdk: '>=2.12.0 <4.0.0' + sdk: '>=2.12.0 <3.0.0' dev_dependencies: benchmark_harness: any From c6aac01a98c74df11cd88f6bded85d62181e42de Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Tue, 14 Nov 2023 13:53:06 -0600 Subject: [PATCH 15/16] Localize concept of "margin" --- lib/src/r_tree/node.dart | 4 ---- lib/src/r_tree/r_tree.dart | 27 +++++++++++++-------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/lib/src/r_tree/node.dart b/lib/src/r_tree/node.dart index 50b9702..5f168dc 100644 --- a/lib/src/r_tree/node.dart +++ b/lib/src/r_tree/node.dart @@ -40,8 +40,6 @@ abstract class Node implements RTreeContributor { @override Rectangle get rect => _minimumBoundingRect; - void setRect(Rectangle rect) => _minimumBoundingRect = rect; - Node(this.branchFactor); /// Returns an iterable of all items within [searchRect] @@ -92,8 +90,6 @@ abstract class Node implements RTreeContributor { num area() => rect.area(); - num get margin => (rect.right - rect.left) + (rect.bottom - rect.top); - /// Adds the rectangle containing [item] to this node's covered rectangle void include(RTreeContributor item) { _minimumBoundingRect = _minimumBoundingRect == noMBR ? item.rect : rect.boundingBox(item.rect); diff --git a/lib/src/r_tree/r_tree.dart b/lib/src/r_tree/r_tree.dart index 3260128..3870811 100644 --- a/lib/src/r_tree/r_tree.dart +++ b/lib/src/r_tree/r_tree.dart @@ -276,7 +276,7 @@ class RTree { final bbox1 = _boundingBoxForDistribution(node, 0, i); final bbox2 = _boundingBoxForDistribution(node, i, M); - final intersection = bbox1.rect.intersection(bbox2.rect); + final intersection = bbox1.intersection(bbox2); final overlap = intersection != null ? intersection.area() : 0; final area = bbox1.area() + bbox2.area(); @@ -323,29 +323,28 @@ class RTree { final leftBoundingBox = _boundingBoxForDistribution(node, 0, m); final rightBoundingBox = _boundingBoxForDistribution(node, M - m, M); - var margin = leftBoundingBox.margin + rightBoundingBox.margin; + num calculateMargin(Rectangle rect) => (rect.right - rect.left) + (rect.bottom - rect.top); + + var margin = calculateMargin(leftBoundingBox) + calculateMargin(rightBoundingBox); for (var i = m; i < M - m; i++) { - leftBoundingBox.extend(node is LeafNode ? node.children[i].rect : node.children[i].rect); - margin += leftBoundingBox.margin; + leftBoundingBox.boundingBox(node is LeafNode ? node.children[i].rect : node.children[i].rect); + margin += calculateMargin(leftBoundingBox); } for (var i = M - m - 1; i >= m; i--) { - rightBoundingBox.extend(node.children[i].rect); - margin += rightBoundingBox.margin; + rightBoundingBox.boundingBox(node.children[i].rect); + margin += calculateMargin(rightBoundingBox); } return margin; } - Node _boundingBoxForDistribution(Node node, int startChild, int stopChild) { - final destNode = LeafNode(_branchFactor); - destNode.setRect(node.children[0].rect); - - for (var i = startChild; i < stopChild; i++) { - destNode.extend(node.children[i].rect); - } - return destNode; + Rectangle _boundingBoxForDistribution(Node node, int startChild, int stopChild) { + return node.children.sublist(startChild, stopChild).fold( + node.children[startChild].rect, + (previousValue, element) => previousValue.boundingBox(element.rect), + ); } void _resetRoot() { From e842cd224371c7ba734c505af472fa212527277f Mon Sep 17 00:00:00 2001 From: Travis Sanderson Date: Tue, 14 Nov 2023 13:55:23 -0600 Subject: [PATCH 16/16] Always return a List and update the return type accordingly --- lib/src/r_tree/leaf_node.dart | 5 +++-- lib/src/r_tree/node.dart | 2 +- lib/src/r_tree/non_leaf_node.dart | 2 +- lib/src/r_tree/r_tree.dart | 11 +++++++---- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/src/r_tree/leaf_node.dart b/lib/src/r_tree/leaf_node.dart index 797a7e6..07e0d5c 100644 --- a/lib/src/r_tree/leaf_node.dart +++ b/lib/src/r_tree/leaf_node.dart @@ -45,9 +45,10 @@ class LeafNode extends Node { } @override - Iterable> search(Rectangle searchRect, bool Function(E item)? shouldInclude) { + List> search(Rectangle searchRect, bool Function(E item)? shouldInclude) { return _items - .where((item) => item.rect.overlaps(searchRect) && (shouldInclude == null || shouldInclude(item.value))); + .where((item) => item.rect.overlaps(searchRect) && (shouldInclude == null || shouldInclude(item.value))) + .toList(); } @override diff --git a/lib/src/r_tree/node.dart b/lib/src/r_tree/node.dart index 5f168dc..9ee30db 100644 --- a/lib/src/r_tree/node.dart +++ b/lib/src/r_tree/node.dart @@ -43,7 +43,7 @@ abstract class Node implements RTreeContributor { Node(this.branchFactor); /// Returns an iterable of all items within [searchRect] - Iterable> search(Rectangle searchRect, bool Function(E item)? shouldInclude); + List> search(Rectangle searchRect, bool Function(E item)? shouldInclude); /// Inserts [item] into the node. If the insertion causes a split to occur, the split node will be returned, otherwise null is returned. Node? insert(RTreeDatum item); diff --git a/lib/src/r_tree/non_leaf_node.dart b/lib/src/r_tree/non_leaf_node.dart index 5ca38c7..a89ada2 100644 --- a/lib/src/r_tree/non_leaf_node.dart +++ b/lib/src/r_tree/non_leaf_node.dart @@ -43,7 +43,7 @@ class NonLeafNode extends Node { } @override - Iterable> search(Rectangle searchRect, bool Function(E item)? shouldInclude) { + List> search(Rectangle searchRect, bool Function(E item)? shouldInclude) { final overlappingLeafs = >[]; for (final childNode in _childNodes) { diff --git a/lib/src/r_tree/r_tree.dart b/lib/src/r_tree/r_tree.dart index 3870811..218c976 100644 --- a/lib/src/r_tree/r_tree.dart +++ b/lib/src/r_tree/r_tree.dart @@ -65,13 +65,16 @@ class RTree { } } - // Returns all items whose rectangles overlap [searchRect] - // Note: Rectangles that share only a border are not considered to overlap - Iterable> search(Rectangle searchRect, {bool Function(E item)? shouldInclude}) { + /// Returns all items whose rectangles overlap [searchRect] + /// If [shouldInclude] is specified, each item will be passed to the + /// method and excluded if [shouldInclude] evaluates to false. + /// + /// Note: Rectangles that share only a border are not considered to overlap + List> search(Rectangle searchRect, {bool Function(E item)? shouldInclude}) { shouldInclude ??= (_) => true; if (_root is LeafNode) { - return _root.search(searchRect, shouldInclude).toList(); + return _root.search(searchRect, shouldInclude); } return _root.search(searchRect, shouldInclude);