From 5a8b10352d531a7a445b6e1e3bf1d4a61c56ed03 Mon Sep 17 00:00:00 2001 From: Stuart Turner Date: Thu, 14 Nov 2024 07:20:46 -0600 Subject: [PATCH] Modernize project --- .editorconfig | 212 ++++++++++++++------- .github/workflows/build.yml | 46 ++--- Directory.Build.props | 33 ++-- Directory.Packages.props | 21 ++ RBush.Test/.editorconfig | 9 + RBush.Test/KnnTests.cs | 18 +- RBush.Test/Point.cs | 15 +- RBush.Test/RBush.Test.csproj | 13 +- RBush.Test/RBushTests.cs | 120 ++++++------ RBush.sln | 3 +- RBush/ArgumentNullException.cs | 29 +++ RBush/Envelope.cs | 34 ++-- RBush/RBush.Node.cs | 8 +- RBush/RBush.Utilities.cs | 29 +-- RBush/RBush.cs | 68 +++---- RBush/RBush.csproj | 99 +++++----- RBush/{RBush.Knn.cs => RBushExtensions.cs} | 2 + 17 files changed, 445 insertions(+), 314 deletions(-) create mode 100644 Directory.Packages.props create mode 100644 RBush.Test/.editorconfig create mode 100644 RBush/ArgumentNullException.cs rename RBush/{RBush.Knn.cs => RBushExtensions.cs} (98%) diff --git a/.editorconfig b/.editorconfig index 0525433..47356ca 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,51 +5,12 @@ root = true [*] indent_style = tab insert_final_newline = true - -# Build scripts -[*.{yml,yaml}] -indent_style = spaces -indent_size = 2 - -# Code files -[*.{cs,csx,vb,vbx}] +tab_width = 4 indent_size = 4 -charset = utf-8-bom - -# XML project files -[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] -indent_size = 2 - -# Dotnet code style settings: -[*.{cs,tt}] - -# IDE0055: Fix formatting -dotnet_diagnostic.IDE0055.severity = warning - -# Sort using and Import directives with System.* appearing first -dotnet_sort_system_directives_first = true -dotnet_separate_import_directive_groups = false - -# require accessibility modifiers -dotnet_style_require_accessibility_modifiers = for_non_interface_members:error - -# Avoid "this." and "Me." if not necessary -dotnet_style_qualification_for_field = false:refactoring -dotnet_style_qualification_for_property = false:refactoring -dotnet_style_qualification_for_method = false:refactoring -dotnet_style_qualification_for_event = false:refactoring +charset = utf-8 -# Use language keywords instead of framework type names for type references -dotnet_style_predefined_type_for_locals_parameters_members = true:error -dotnet_style_predefined_type_for_member_access = true:suggestion -# Suggest more modern language features when available -dotnet_style_object_initializer = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_coalesce_expression = true:suggestion -dotnet_style_null_propagation = true:suggestion -dotnet_style_explicit_tuple_names = true:suggestion -dotnet_style_prefer_compound_assignment = true:warning +### Naming rules: ### # Constants are PascalCase dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion @@ -83,10 +44,10 @@ dotnet_naming_symbols.non_private_static_fields.required_modifiers = static dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case -# Static fields are camelCase and start with s_ -dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion -dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields -dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style +# Static fields are s_camelCase +dotnet_naming_rule.static_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.static_fields_should_be_pascal_case.symbols = static_fields +dotnet_naming_rule.static_fields_should_be_pascal_case.style = static_field_style dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static @@ -131,8 +92,81 @@ dotnet_naming_symbols.all_members.applicable_kinds = * dotnet_naming_style.pascal_case_style.capitalization = pascal_case -# CSharp code style settings: + +### Dotnet code style settings: ### + +# Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +# require accessibility modifiers +dotnet_style_require_accessibility_modifiers = for_non_interface_members:error + +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = false:refactoring +dotnet_style_qualification_for_property = false:refactoring +dotnet_style_qualification_for_method = false:refactoring +dotnet_style_qualification_for_event = false:refactoring + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:error +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Initializers +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:warning + +# Null checks +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning + +# Tuple Naming +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion + +# Assignments +dotnet_style_prefer_conditional_expression_over_assignment = true:error +dotnet_style_prefer_conditional_expression_over_return = true:none +dotnet_style_prefer_compound_assignment = true:warning + +# Parenthesis +dotnet_code_quality_unused_parameters = all:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion + +# Miscellaneous +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_simplified_boolean_expressions = true:warning +dotnet_style_prefer_simplified_interpolation = true:warning +dotnet_style_namespace_match_folder = true:warning +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_readonly_field = true:warning + +# New-line preferences +dotnet_style_allow_multiple_blank_lines_experimental = false:warning +dotnet_style_allow_statement_immediately_after_block_experimental = false:warning +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false:warning +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false:warning + +# Build scripts +[*.{yml,yaml}] +indent_style = spaces +indent_size = 2 + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# Code files [*.cs] + +## C# style settings: + # Newline settings csharp_new_line_before_open_brace = all csharp_new_line_before_else = true @@ -156,25 +190,18 @@ csharp_style_var_when_type_is_apparent = true:suggestion csharp_style_var_elsewhere = true:suggestion # Prefer method-like constructs to have a block body -csharp_style_expression_bodied_methods = true:suggestion +csharp_style_expression_bodied_methods = false:none csharp_style_expression_bodied_constructors = false:none csharp_style_expression_bodied_operators = false:none +# Prefer local method constructs to have a block body +csharp_style_expression_bodied_local_functions = true:suggestion + # Prefer property-like constructs to have an expression-body csharp_style_expression_bodied_properties = true:suggestion csharp_style_expression_bodied_indexers = true:suggestion csharp_style_expression_bodied_accessors = true:suggestion -# Suggest more modern language features when available -csharp_style_pattern_matching_over_is_with_cast_check = true:warning -csharp_style_pattern_matching_over_as_with_null_check = true:warning -csharp_style_inlined_variable_declaration = true:warning -csharp_style_throw_expression = true:warning -csharp_style_conditional_delegate_call = true:warning -csharp_style_prefer_switch_expression = true:warning -csharp_prefer_simple_using_statement = true:suggestion -csharp_style_namespace_declarations = file_scoped:error - # Space preferences csharp_space_after_cast = false csharp_space_after_colon_in_inheritance_clause = true @@ -200,24 +227,73 @@ csharp_space_between_parentheses = false csharp_space_between_square_brackets = false # Blocks are allowed -csharp_prefer_braces = true:silent +csharp_prefer_braces = when_multiline:silent csharp_preserve_single_line_blocks = true:silent csharp_preserve_single_line_statements = true:silent +# Pattern Matching +csharp_style_prefer_pattern_matching = true:warning +csharp_style_prefer_not_pattern = true:warning +csharp_style_prefer_extended_property_pattern = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:warning + +# Namespace +csharp_style_namespace_declarations = file_scoped:error +csharp_using_directive_placement = outside_namespace:warning + +# Suggest more modern language features when available +csharp_prefer_simple_default_expression = true:warning +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_static_local_function = true:suggestion +csharp_style_conditional_delegate_call = true:warning +csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_inlined_variable_declaration = true:warning +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_primary_constructors = true:warning +csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion +csharp_style_prefer_switch_expression = true:warning +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_throw_expression = true:warning +csharp_style_unused_value_assignment_preference = discard_variable:warning +csharp_style_unused_value_expression_statement_preference = discard_variable:warning + +# New Lines +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent + +# Style Analytics +dotnet_analyzer_diagnostic.category-Style.severity = warning + +# XML Documentation dotnet_diagnostic.CS0105.severity = error # CS0105: Using directive is unnecessary. dotnet_diagnostic.CS1573.severity = error # CS1573: Missing XML comment for parameter dotnet_diagnostic.CS1591.severity = error # CS1591: Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1712.severity = error # CS1712: Type parameter has no matching typeparam tag in the XML comment (but other type parameters do) +# Async dotnet_diagnostic.CS1998.severity = error # CS1998: Async method lacks 'await' operators and will run synchronously dotnet_diagnostic.CS4014.severity = error # CS4014: Because this call is not awaited, execution of the current method continues before the call is completed +dotnet_diagnostic.CA2007.severity = none # CA2007: Consider calling ConfigureAwait on the awaited task + +# Dispose things need disposing +dotnet_diagnostic.CA2000.severity = error # CA2000: Dispose objects before losing scope -dotnet_diagnostic.CS8602.severity = error # CS8602: Dereference of a possibly null reference. -dotnet_diagnostic.CS8603.severity = error # CS8603: Possible null reference return -dotnet_diagnostic.CS8604.severity = error # CS8604: Possible null reference argument -dotnet_diagnostic.CS8618.severity = error # CS8618: Non-nullable field is uninitialized. Consider declaring as nullable. +dotnet_diagnostic.CA1034.severity = none # CA1034: Nested types should not be visible +dotnet_diagnostic.CA1515.severity = none # CA1515: Consider making public types internal +dotnet_diagnostic.CA1724.severity = none -dotnet_diagnostic.MA0016.severity = none # MA0016: Prefer returning collection abstraction instead of implementation -dotnet_diagnostic.MA0048.severity = none # MA0048: File name must match type name -dotnet_diagnostic.MA0049.severity = none # MA0049: Type name should not match containing namespace -dotnet_diagnostic.MA0051.severity = none # MA0051: Method is too long -dotnet_diagnostic.MA0071.severity = silent # MA0071: Avoid using redundant else +dotnet_diagnostic.MA0049.severity = none +dotnet_diagnostic.IDE0305.severity = none diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7c4252b..5cbcb13 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,14 @@ name: Build -on: [push, pull_request] +on: + workflow_dispatch: + push: + branches: + - 'master' + paths-ignore: + - '**/readme.md' + pull_request: + types: [opened, synchronize, reopened] jobs: build: @@ -8,43 +16,25 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v4 with: - languages: 'csharp' - - - name: Setup .NET 2.2 - uses: actions/setup-dotnet@v2 - with: - dotnet-version: '2.2.x' - - name: Setup .NET 3.1 - uses: actions/setup-dotnet@v2 - with: - dotnet-version: '3.1.x' - - name: Setup .NET 6.0 - uses: actions/setup-dotnet@v2 - with: - dotnet-version: '6.0.x' + dotnet-version: | + 8.0.x - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build -c Release --no-restore - name: Test - run: dotnet test -c Release --no-build --verbosity normal + run: dotnet test -c Release --no-build --logger GitHubActions - - name: Test Report - uses: dorny/test-reporter@v1 - if: success() || failure() # run this step even if previous step failed + - name: Upload coverage reports to Codecov with GitHub Action + uses: codecov/codecov-action@v4 with: - name: 'Test report' - path: '**/*.trx' - reporter: 'dotnet-trx' - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + token: ${{ secrets.CODECOV_TOKEN }} - name: Package run: dotnet pack -c Release --no-build -o nupkgs diff --git a/Directory.Build.props b/Directory.Build.props index c873469..b52086b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,16 +1,23 @@ - - latest - enable - enable - latest-recommended - true - + + latest + net8.0 - - true - true - trx%3bLogFileName=$(MSBuildProjectName)-$(TargetFramework).trx - $(MSBuildThisFileDirectory)/TestResults - + enable + $(WarningsAsErrors);nullable; + + enable + + latest-all + true + + true + + + + true + true + true + opencover + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..a297d1d --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,21 @@ + + + true + + + + + + + + + + + + + + + + + + diff --git a/RBush.Test/.editorconfig b/RBush.Test/.editorconfig new file mode 100644 index 0000000..345b270 --- /dev/null +++ b/RBush.Test/.editorconfig @@ -0,0 +1,9 @@ +[*.cs] + +dotnet_diagnostic.CS1573.severity = none # CS1573: Missing XML comment for parameter +dotnet_diagnostic.CS1591.severity = none # CS1591: Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1712.severity = none # CS1712: Type parameter has no matching typeparam tag in the XML comment (but other type parameters do) + +dotnet_diagnostic.CA1707.severity = none # CA1707: Identifiers should not contain underscores +dotnet_diagnostic.CA1814.severity = none +dotnet_diagnostic.CA1822.severity = none # CA1822: Mark members as static diff --git a/RBush.Test/KnnTests.cs b/RBush.Test/KnnTests.cs index 9bb8c42..d0a425d 100644 --- a/RBush.Test/KnnTests.cs +++ b/RBush.Test/KnnTests.cs @@ -1,4 +1,4 @@ -using Xunit; +using Xunit; namespace RBush.Test; @@ -22,7 +22,7 @@ public class KnnTests {99,3,103,5}, {41,92,44,96}, {79,40,79,41}, {29,2,29,4}, }); - [Fact] + [Test] public void FindsNNeighbors() { var bush = new RBush(); @@ -35,19 +35,19 @@ public void FindsNNeighbors() Assert.Equal(expected, result); } - [Fact] + [Test] public void DoesNotThrowIfRequestingTooManyItems() { var bush = new RBush(); bush.BulkLoad(s_points); - bush.Knn(1000, 40, 40); + _ = bush.Knn(1000, 40, 40); } /// /// This test is not correct in original javascript library /// - [Fact] + [Test] public void FindAllNeighborsForMaxDistance() { var bush = new RBush(); @@ -61,7 +61,7 @@ public void FindAllNeighborsForMaxDistance() Assert.Equal(expected, result); } - [Fact] + [Test] public void FindNNeighborsForMaxDistance() { var bush = new RBush(); @@ -76,13 +76,13 @@ public void FindNNeighborsForMaxDistance() Assert.Equal(expected, result); } - [Fact] + [Test] public void DoesNotThrowIfRequestingTooManyItemsForMaxDistance() { var bush = new RBush(); bush.BulkLoad(s_points); - bush.Knn(1000, 40, 40, maxDistance: 10); + _ = bush.Knn(1000, 40, 40, maxDistance: 10); } private static readonly Point[] s_richData = Point.CreatePoints( @@ -92,7 +92,7 @@ public void DoesNotThrowIfRequestingTooManyItemsForMaxDistance() { 4, 2, 4, 2 }, { 2, 4, 2, 4 }, { 5, 3, 5, 3 }, }); - [Fact] + [Test] public void FindNeighborsThatSatisfyAGivenPredicate() { var bush = new RBush(); diff --git a/RBush.Test/Point.cs b/RBush.Test/Point.cs index 90d5e2c..5f590a6 100644 --- a/RBush.Test/Point.cs +++ b/RBush.Test/Point.cs @@ -1,17 +1,14 @@ -namespace RBush.Test; +namespace RBush.Test; -internal sealed class Point : ISpatialData, IEquatable +internal sealed class Point(double minX, double minY, double maxX, double maxY) : ISpatialData, IEquatable { - private readonly Envelope _envelope; - - public Point(double minX, double minY, double maxX, double maxY) - { - _envelope = new Envelope( + private readonly Envelope _envelope = + new( MinX: minX, MinY: minY, MaxX: maxX, - MaxY: maxY); - } + MaxY: maxY + ); public ref readonly Envelope Envelope => ref _envelope; diff --git a/RBush.Test/RBush.Test.csproj b/RBush.Test/RBush.Test.csproj index 65c42db..f05d7f4 100644 --- a/RBush.Test/RBush.Test.csproj +++ b/RBush.Test/RBush.Test.csproj @@ -1,13 +1,16 @@ - + - netcoreapp2.2;netcoreapp3.1;net6.0 + net8.0 + false - - - + + + + + diff --git a/RBush.Test/RBushTests.cs b/RBush.Test/RBushTests.cs index 68aeb01..a8b5fad 100644 --- a/RBush.Test/RBushTests.cs +++ b/RBush.Test/RBushTests.cs @@ -1,4 +1,4 @@ -using Xunit; +using Xunit; namespace RBush.Test; @@ -24,10 +24,10 @@ private static List GetPoints(int cnt) => maxY: i)) .ToList(); - [Fact] + [Test] public void RootLeafSplitWorks() { - var data = RBushTests.GetPoints(12); + var data = GetPoints(12); var tree = new RBush(); for (var i = 0; i < 9; i++) @@ -54,7 +54,7 @@ public void RootLeafSplitWorks() Assert.Equal(9, tree.Root.Envelope.MaxY); } - [Fact] + [Test] public void InsertTestData() { var tree = new RBush(); @@ -70,7 +70,7 @@ public void InsertTestData() tree.Envelope); } - [Fact] + [Test] public void BulkLoadTestData() { var tree = new RBush(); @@ -81,7 +81,7 @@ public void BulkLoadTestData() .SetEquals(tree.Search())); } - [Fact] + [Test] public void BulkLoadSplitsTreeProperly() { var tree = new RBush(maxEntries: 4); @@ -92,10 +92,10 @@ public void BulkLoadSplitsTreeProperly() Assert.Equal(4, tree.Root.Height); } - [Fact] + [Test] public void BulkLoadMergesTreesProperly() { - var smaller = RBushTests.GetPoints(10); + var smaller = GetPoints(10); var tree1 = new RBush(maxEntries: 4); tree1.BulkLoad(smaller); tree1.BulkLoad(s_points); @@ -112,16 +112,16 @@ public void BulkLoadMergesTreesProperly() Assert.True(allPoints.SetEquals(tree2.Search())); } - [Fact] + [Test] public void SearchReturnsEmptyResultIfNothingFound() { var tree = new RBush(maxEntries: 4); tree.BulkLoad(s_points); - Assert.Equal(Array.Empty(), tree.Search(new Envelope(200, 200, 210, 210))); + Assert.Equal([], tree.Search(new Envelope(200, 200, 210, 210))); } - [Fact] + [Test] public void SearchReturnsMatchingResults() { var tree = new RBush(maxEntries: 4); @@ -134,7 +134,7 @@ public void SearchReturnsMatchingResults() Assert.True(shouldFindPoints.SetEquals(tree.Search(searchEnvelope))); } - [Fact] + [Test] public void BasicRemoveTest() { var tree = new RBush(maxEntries: 4); @@ -142,13 +142,13 @@ public void BasicRemoveTest() var len = s_points.Length; - tree.Delete(s_points[0]); - tree.Delete(s_points[1]); - tree.Delete(s_points[2]); + _ = tree.Delete(s_points[0]); + _ = tree.Delete(s_points[1]); + _ = tree.Delete(s_points[2]); - tree.Delete(s_points[len - 1]); - tree.Delete(s_points[len - 2]); - tree.Delete(s_points[len - 3]); + _ = tree.Delete(s_points[len - 1]); + _ = tree.Delete(s_points[len - 2]); + _ = tree.Delete(s_points[len - 3]); var shouldFindPoints = new HashSet(s_points .Skip(3).Take(len - 6)); @@ -159,39 +159,39 @@ public void BasicRemoveTest() tree.Envelope); } - [Fact] + [Test] public void NonExistentItemCanBeDeleted() { var tree = new RBush(maxEntries: 4); tree.BulkLoad(s_points); - tree.Delete(new Point(13, 13, 13, 13)); + _ = tree.Delete(new Point(13, 13, 13, 13)); Assert.Equal(s_points.Length, tree.Count); } - [Fact] + [Test] public void DeleteTreeIsEmptyShouldNotThrow() { var tree = new RBush(); - tree.Delete(new Point(1, 1, 1, 1)); + _ = tree.Delete(new Point(1, 1, 1, 1)); Assert.Equal(0, tree.Count); } - [Fact] + [Test] public void DeleteDeletingLastPointShouldNotThrow() { var tree = new RBush(); var p = new Point(1, 1, 1, 1); tree.Insert(p); - tree.Delete(p); + _ = tree.Delete(p); Assert.Equal(0, tree.Count); } - [Fact] + [Test] public void ClearWorks() { var tree = new RBush(maxEntries: 4); @@ -202,15 +202,15 @@ public void ClearWorks() Assert.Empty(tree.Root.Children); } - [Fact] + [Test] public void TestSearchAfterInsert() { var maxEntries = 9; var tree = new RBush(maxEntries); - var firstSet = s_points.Take(maxEntries); - var firstSetEnvelope = - firstSet.Aggregate(Envelope.EmptyBounds, (e, p) => e.Extend(p.Envelope)); + var firstSet = s_points.Take(maxEntries).ToList(); + var firstSetEnvelope = firstSet + .Aggregate(Envelope.EmptyBounds, (e, p) => e.Extend(p.Envelope)); foreach (var p in firstSet) tree.Insert(p); @@ -219,22 +219,22 @@ public void TestSearchAfterInsert() .SetEquals(tree.Search(firstSetEnvelope))); } - [Fact] + [Test] public void TestSearchAfterInsertWithSplitRoot() { var maxEntries = 4; var tree = new RBush(maxEntries); - var numFirstSet = maxEntries * maxEntries + 2; // Split-root will occur twice. + var numFirstSet = (maxEntries * maxEntries) + 2; // Split-root will occur twice. var firstSet = s_points.Take(numFirstSet); foreach (var p in firstSet) tree.Insert(p); var numExtraPoints = 5; - var extraPointsSet = s_points.Skip(s_points.Length - numExtraPoints); - var extraPointsSetEnvelope = - extraPointsSet.Aggregate(Envelope.EmptyBounds, (e, p) => e.Extend(p.Envelope)); + var extraPointsSet = s_points.Skip(s_points.Length - numExtraPoints).ToList(); + var extraPointsSetEnvelope = extraPointsSet + .Aggregate(Envelope.EmptyBounds, (e, p) => e.Extend(p.Envelope)); foreach (var p in extraPointsSet) tree.Insert(p); @@ -245,21 +245,21 @@ public void TestSearchAfterInsertWithSplitRoot() .SetEquals(tree.Search(extraPointsSetEnvelope))); } - [Fact] + [Test] public void TestSearchAfterBulkLoadWithSplitRoot() { var maxEntries = 4; var tree = new RBush(maxEntries); - var numFirstSet = maxEntries * maxEntries + 2; // Split-root will occur twice. + var numFirstSet = (maxEntries * maxEntries) + 2; // Split-root will occur twice. var firstSet = s_points.Take(numFirstSet); tree.BulkLoad(firstSet); var numExtraPoints = 5; - var extraPointsSet = s_points.Skip(s_points.Length - numExtraPoints); - var extraPointsSetEnvelope = - extraPointsSet.Aggregate(Envelope.EmptyBounds, (e, p) => e.Extend(p.Envelope)); + var extraPointsSet = s_points.Skip(s_points.Length - numExtraPoints).ToList(); + var extraPointsSetEnvelope = extraPointsSet + .Aggregate(Envelope.EmptyBounds, (e, p) => e.Extend(p.Envelope)); tree.BulkLoad(extraPointsSet); @@ -269,7 +269,7 @@ public void TestSearchAfterBulkLoadWithSplitRoot() .SetEquals(tree.Search(extraPointsSetEnvelope))); } - [Fact] + [Test] public void AdditionalRemoveTest() { var tree = new RBush(); @@ -279,24 +279,24 @@ public void AdditionalRemoveTest() tree.Insert(p); foreach (var p in s_points.Take(numDelete)) - tree.Delete(p); + _ = tree.Delete(p); Assert.Equal(s_points.Length - numDelete, tree.Count); Assert.True(new HashSet(s_points.Skip(numDelete)) .SetEquals(tree.Search())); } - [Fact] + [Test] public void BulkLoadAfterDeleteTest1() { - var pts = RBushTests.GetPoints(20); + var pts = GetPoints(20); var ptsDelete = pts.Take(18); var tree = new RBush(maxEntries: 4); tree.BulkLoad(pts); foreach (var item in ptsDelete) - tree.Delete(item); + _ = tree.Delete(item); tree.BulkLoad(ptsDelete); @@ -304,17 +304,17 @@ public void BulkLoadAfterDeleteTest1() Assert.True(new HashSet(pts).SetEquals(tree.Search())); } - [Fact] + [Test] public void BulkLoadAfterDeleteTest2() { - var pts = RBushTests.GetPoints(20); + var pts = GetPoints(20); var ptsDelete = pts.Take(4); var tree = new RBush(maxEntries: 4); tree.BulkLoad(pts); foreach (var item in ptsDelete) - tree.Delete(item); + _ = tree.Delete(item); tree.BulkLoad(ptsDelete); @@ -323,18 +323,18 @@ public void BulkLoadAfterDeleteTest2() .SetEquals(tree.Search())); } - [Fact] + [Test] public void InsertAfterDeleteTest1() { - var pts = RBushTests.GetPoints(20); - var ptsDelete = pts.Take(18); + var pts = GetPoints(20); + var ptsDelete = pts.Take(18).ToList(); var tree = new RBush(maxEntries: 4); foreach (var item in pts) tree.Insert(item); foreach (var item in ptsDelete) - tree.Delete(item); + _ = tree.Delete(item); foreach (var item in ptsDelete) tree.Insert(item); @@ -344,18 +344,18 @@ public void InsertAfterDeleteTest1() .SetEquals(tree.Search())); } - [Fact] + [Test] public void InsertAfterDeleteTest2() { - var pts = RBushTests.GetPoints(20); - var ptsDelete = pts.Take(4); + var pts = GetPoints(20); + var ptsDelete = pts.Take(4).ToList(); var tree = new RBush(maxEntries: 4); foreach (var item in pts) tree.Insert(item); foreach (var item in ptsDelete) - tree.Delete(item); + _ = tree.Delete(item); foreach (var item in ptsDelete) tree.Insert(item); @@ -365,8 +365,8 @@ public void InsertAfterDeleteTest2() .SetEquals(tree.Search())); } - private readonly List _missingEnvelopeTestData = new() - { + private readonly List _missingEnvelopeTestData = + [ new Point(minX: 35.0457204123358, minY: 31.5946330633669, maxX: 35.1736414417038, maxY: 31.7658263429689), new Point(minX: 35.0011136524732, minY: 31.6701999643473, maxX: 35.0119650302309, maxY: 31.6763344627552), new Point(minX: 35.4519996266397, minY: 33.0521061332025, maxX: 35.6225745715679, maxY: 33.2873426178667), @@ -461,9 +461,9 @@ public void InsertAfterDeleteTest2() new Point(minX: 34.6095401534748, minY: 31.7162092103111, maxX: 35.0591449537042, maxY: 32.0383069680269), new Point(minX: 34.7421834416939, minY: 32.003746842197, maxX: 35.0354837295605, maxY: 32.1466473164001), new Point(minX: 34.7905250664059, minY: 32.1179577036489, maxX: 35.0581623045431, maxY: 32.3417009250043), - }; + ]; - [Fact] + [Test] public void MissingEnvelopeTestInsertIndividually() { var tree = new RBush(); @@ -480,7 +480,7 @@ public void MissingEnvelopeTestInsertIndividually() actual: tree.Search(envelope).Count); } - [Fact] + [Test] public void TestBulk() { var tree = new RBush(); diff --git a/RBush.sln b/RBush.sln index 832a1ea..026510d 100644 --- a/RBush.sln +++ b/RBush.sln @@ -5,12 +5,13 @@ VisualStudioVersion = 17.2.32616.157 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RBush", "RBush\RBush.csproj", "{24061D07-5EFA-4D72-9617-0C6671280FDF}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6EA781FE-7457-4266-93E4-6C2751DCB4CF}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".root", ".root", "{6EA781FE-7457-4266-93E4-6C2751DCB4CF}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .github\workflows\build.yml = .github\workflows\build.yml .github\dependabot.yml = .github\dependabot.yml Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props README.md = README.md EndProjectSection EndProject diff --git a/RBush/ArgumentNullException.cs b/RBush/ArgumentNullException.cs new file mode 100644 index 0000000..60def7d --- /dev/null +++ b/RBush/ArgumentNullException.cs @@ -0,0 +1,29 @@ +#if !NET6_0_OR_GREATER +#pragma warning disable IDE0005 // Using directive is unnecessary. +global using ArgumentNullException = RBush.ArgumentNullException; +#pragma warning restore IDE0005 // Using directive is unnecessary. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace RBush; + +[Browsable(false)] +[ExcludeFromCodeCoverage] +internal static class ArgumentNullException +{ + /// Throws an if is null. + /// The reference type argument to validate as non-null. + /// The name of the parameter with which corresponds. + public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) + { + if (argument is null) + Throw(paramName); + } + + [DoesNotReturn] + private static void Throw(string? paramName) => + throw new System.ArgumentNullException(paramName); +} +#endif diff --git a/RBush/Envelope.cs b/RBush/Envelope.cs index da9d2da..1bf0fe1 100644 --- a/RBush/Envelope.cs +++ b/RBush/Envelope.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace RBush; @@ -37,10 +37,10 @@ public readonly record struct Envelope( /// Does not affect the current bounding box. public Envelope Extend(in Envelope other) => new( - MinX: Math.Min(this.MinX, other.MinX), - MinY: Math.Min(this.MinY, other.MinY), - MaxX: Math.Max(this.MaxX, other.MaxX), - MaxY: Math.Max(this.MaxY, other.MaxY)); + MinX: Math.Min(MinX, other.MinX), + MinY: Math.Min(MinY, other.MinY), + MaxX: Math.Max(MaxX, other.MaxX), + MaxY: Math.Max(MaxY, other.MaxY)); /// /// Intersects a bounding box to only include the common area @@ -51,10 +51,10 @@ public Envelope Extend(in Envelope other) => /// Does not affect the current bounding box. public Envelope Intersection(in Envelope other) => new( - MinX: Math.Max(this.MinX, other.MinX), - MinY: Math.Max(this.MinY, other.MinY), - MaxX: Math.Min(this.MaxX, other.MaxX), - MaxY: Math.Min(this.MaxY, other.MaxY)); + MinX: Math.Max(MinX, other.MinX), + MinY: Math.Max(MinY, other.MinY), + MaxX: Math.Min(MaxX, other.MaxX), + MaxY: Math.Min(MaxY, other.MaxY)); /// /// Determines whether is contained @@ -67,10 +67,10 @@ public Envelope Intersection(in Envelope other) => /// otherwise. /// public bool Contains(in Envelope other) => - this.MinX <= other.MinX && - this.MinY <= other.MinY && - this.MaxX >= other.MaxX && - this.MaxY >= other.MaxY; + MinX <= other.MinX && + MinY <= other.MinY && + MaxX >= other.MaxX && + MaxY >= other.MaxY; /// /// Determines whether intersects @@ -83,10 +83,10 @@ public bool Contains(in Envelope other) => /// otherwise. /// public bool Intersects(in Envelope other) => - this.MinX <= other.MaxX && - this.MinY <= other.MaxY && - this.MaxX >= other.MinX && - this.MaxY >= other.MinY; + MinX <= other.MaxX && + MinY <= other.MaxY && + MaxX >= other.MinX && + MaxY >= other.MinY; /// /// A bounding box that contains the entire 2-d plane. diff --git a/RBush/RBush.Node.cs b/RBush/RBush.Node.cs index 49f09ea..32db79b 100644 --- a/RBush/RBush.Node.cs +++ b/RBush/RBush.Node.cs @@ -1,4 +1,4 @@ -namespace RBush; +namespace RBush; public partial class RBush { @@ -12,8 +12,8 @@ public class Node : ISpatialData internal Node(List items, int height) { - this.Height = height; - this.Items = items; + Height = height; + Items = items; ResetEnvelope(); } @@ -25,7 +25,7 @@ internal void Add(ISpatialData node) internal void Remove(ISpatialData node) { - Items.Remove(node); + _ = Items.Remove(node); ResetEnvelope(); } diff --git a/RBush/RBush.Utilities.cs b/RBush/RBush.Utilities.cs index 22cbf37..1a2594b 100644 --- a/RBush/RBush.Utilities.cs +++ b/RBush/RBush.Utilities.cs @@ -1,4 +1,4 @@ -namespace RBush; +namespace RBush; public partial class RBush { @@ -13,7 +13,7 @@ public partial class RBush private List DoSearch(in Envelope boundingBox) { if (!Root.Envelope.Intersects(boundingBox)) - return new List(); + return []; var intersections = new List(); var queue = new Queue(); @@ -49,7 +49,7 @@ private List DoSearch(in Envelope boundingBox) private List FindCoveringArea(in Envelope area, int depth) { var path = new List(); - var node = this.Root; + var node = Root; while (true) { @@ -67,7 +67,9 @@ private List FindCoveringArea(in Envelope area, int depth) if (newArea == nextArea && i.Envelope.Area >= next.Envelope.Area) + { continue; + } next = i; nextArea = newArea; @@ -81,7 +83,7 @@ private void Insert(ISpatialData data, int depth) { var path = FindCoveringArea(data.Envelope, depth); - var insertNode = path[path.Count - 1]; + var insertNode = path[^1]; insertNode.Add(data); while (--depth >= 0) @@ -95,13 +97,15 @@ private void Insert(ISpatialData data, int depth) path[depth - 1].Add(newNode); } else + { path[depth].ResetEnvelope(); + } } } #region SplitNode private void SplitRoot(Node newNode) => - this.Root = new Node(new List { this.Root, newNode }, this.Root.Height + 1); + Root = new Node([Root, newNode], Root.Height + 1); private Node SplitNode(Node node) { @@ -187,10 +191,9 @@ private Node BuildNodes(ArraySegment data, int height, int maxEntries) return height == 1 ? new Node(data.Cast().ToList(), height) : new Node( - new List - { + [ BuildNodes(data, height - 1, _maxEntries), - }, + ], height); } @@ -231,23 +234,21 @@ private static Envelope GetEnclosingEnvelope(IEnumerable items) { var envelope = Envelope.EmptyBounds; foreach (var data in items) - { envelope = envelope.Extend(data.Envelope); - } + return envelope; } - private List GetAllChildren(List list, Node n) + private static List GetAllChildren(List list, Node n) { if (n.IsLeaf) { - list.AddRange( - n.Items.Cast()); + list.AddRange(n.Items.Cast()); } else { foreach (var node in n.Items.Cast()) - GetAllChildren(list, node); + _ = GetAllChildren(list, node); } return list; diff --git a/RBush/RBush.cs b/RBush/RBush.cs index 8267642..4ec601d 100644 --- a/RBush/RBush.cs +++ b/RBush/RBush.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; namespace RBush; @@ -52,11 +52,11 @@ public RBush(int maxEntries) /// public RBush(int maxEntries, IEqualityComparer comparer) { - this._comparer = comparer; - this._maxEntries = Math.Max(MinimumMaxEntries, maxEntries); - this._minEntries = Math.Max(MinimumMinEntries, (int)Math.Ceiling(this._maxEntries * DefaultFillFactor)); + _comparer = comparer; + _maxEntries = Math.Max(MinimumMaxEntries, maxEntries); + _minEntries = Math.Max(MinimumMinEntries, (int)Math.Ceiling(_maxEntries * DefaultFillFactor)); - this.Clear(); + Clear(); } /// @@ -70,8 +70,8 @@ public RBush(int maxEntries, IEqualityComparer comparer) [MemberNotNull(nameof(Root))] public void Clear() { - this.Root = new Node(new List(), 1); - this.Count = 0; + Root = new Node([], 1); + Count = 0; } /// @@ -81,7 +81,7 @@ public void Clear() /// A list of every element contained in the . /// public IReadOnlyList Search() => - GetAllChildren(new List(), this.Root); + GetAllChildren([], Root); /// /// Get all of the elements from this @@ -103,8 +103,8 @@ public IReadOnlyList Search(in Envelope boundingBox) => /// public void Insert(T item) { - Insert(item, this.Root.Height); - this.Count++; + Insert(item, Root.Height); + Count++; } /// @@ -122,15 +122,15 @@ public void BulkLoad(IEnumerable items) var data = items.ToArray(); if (data.Length == 0) return; - if (this.Root.IsLeaf && - this.Root.Items.Count + data.Length < _maxEntries) + if (Root.IsLeaf && + Root.Items.Count + data.Length < _maxEntries) { foreach (var i in data) Insert(i); return; } - if (data.Length < this._minEntries) + if (data.Length < _minEntries) { foreach (var i in data) Insert(i); @@ -138,32 +138,36 @@ public void BulkLoad(IEnumerable items) } var dataRoot = BuildTree(data); - this.Count += data.Length; + Count += data.Length; - if (this.Root.Items.Count == 0) - this.Root = dataRoot; - else if (this.Root.Height == dataRoot.Height) + if (Root.Items.Count == 0) { - if (this.Root.Items.Count + dataRoot.Items.Count <= this._maxEntries) + Root = dataRoot; + } + else if (Root.Height == dataRoot.Height) + { + if (Root.Items.Count + dataRoot.Items.Count <= _maxEntries) { foreach (var isd in dataRoot.Items) - this.Root.Add(isd); + Root.Add(isd); } else + { SplitRoot(dataRoot); + } } else { - if (this.Root.Height < dataRoot.Height) + if (Root.Height < dataRoot.Height) { #pragma warning disable IDE0180 // netstandard 1.2 doesn't support tuple - var tmp = this.Root; - this.Root = dataRoot; + var tmp = Root; + Root = dataRoot; dataRoot = tmp; #pragma warning restore IDE0180 } - this.Insert(dataRoot, this.Root.Height - dataRoot.Height); + Insert(dataRoot, Root.Height - dataRoot.Height); } } @@ -185,24 +189,24 @@ private bool DoDelete(Node node, T item) if (node.IsLeaf) { var cnt = node.Items.RemoveAll(i => _comparer.Equals((T)i, item)); - if (cnt != 0) - { - Count -= cnt; - node.ResetEnvelope(); - return true; - } - else + if (cnt == 0) return false; + + Count -= cnt; + node.ResetEnvelope(); + return true; + } var flag = false; - foreach (Node n in node.Items) + foreach (var n in node.Items) { - flag |= DoDelete(n, item); + flag |= DoDelete((Node)n, item); } if (flag) node.ResetEnvelope(); + return flag; } } diff --git a/RBush/RBush.csproj b/RBush/RBush.csproj index 964aeae..d30ea6e 100644 --- a/RBush/RBush.csproj +++ b/RBush/RBush.csproj @@ -1,56 +1,47 @@ - - - - netstandard1.2;netcoreapp3.1;net6.0 - RBush - RBush - - - - RBush - Spatial Index data structure; used to make it easier to find data points on a two dimensional plane. - - viceroypenguin - .NET R-Tree Algorithm tree search spatial index - Copyright © 2017-2022 Turning Code, LLC (and others) - - MIT - readme.md - - true - https://github.com/viceroypenguin/RBush - git - - true - snupkg - - true - - - - - - - - - <_Parameter1>RBush.Test - - - - - - - - - - - minor - preview - v - - - - System.Index;System.Range - + + + + net47;netstandard2.0;net8.0 + true + + + + RBush + Spatial Index data structure; used to make it easier to find data points on a two dimensional plane. + + viceroypenguin + .NET R-Tree Algorithm tree search spatial index + Copyright © 2017-2024 Turning Code, LLC (and others) + + MIT + readme.md + + true + https://github.com/viceroypenguin/RBush + git + + true + + + + + + + + + <_Parameter1>RBush.Test + + + + + + + + + + minor + preview.0 + v + diff --git a/RBush/RBush.Knn.cs b/RBush/RBushExtensions.cs similarity index 98% rename from RBush/RBush.Knn.cs rename to RBush/RBushExtensions.cs index a68744a..1e8f466 100644 --- a/RBush/RBush.Knn.cs +++ b/RBush/RBushExtensions.cs @@ -30,6 +30,8 @@ public static IReadOnlyList Knn( Func? predicate = null) where T : ISpatialData { + ArgumentNullException.ThrowIfNull(tree); + var items = maxDistance == null ? tree.Search() : tree.Search(