You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2022/02/12 06:53:50 UTC

[lucenenet] branch master updated: BREAKING: Lucene.Net.Spatial Updates (#619)

This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git


The following commit(s) were added to refs/heads/master by this push:
     new d0d1d54  BREAKING: Lucene.Net.Spatial Updates (#619)
d0d1d54 is described below

commit d0d1d5432a20e1f1114f39eaf8a02b03013bffe3
Author: Shad Storhaug <sh...@shadstorhaug.com>
AuthorDate: Sat Feb 12 13:53:39 2022 +0700

    BREAKING: Lucene.Net.Spatial Updates (#619)
    
    * .build/dependencies.props: Upgraded Spatial4n to 0.4.1.1 and changed packages from Spatial4n.Core and Spatial4n.Core.NTS to Spatial4n.
    
    * Lucene.Net.Spatial: Migrated to non-obsolete members in Spatial4n
    
    * BREAKING: Lucene.Net.Spatial.Prefix.Tree.Cell: Renamed m_outerInstance > m_spatialPrefixTree and constructor parameters outerInstance > spatialPrefixTree
    
    * BREAKING: Lucene.Net.Spatial.Prefix.AbstractPrefixTreeFilter.BaseTermsEnumTransverser: renamed m_outerInstance > m_filter, constructor parameters outerInstance > filter
    
    * BREAKING: Lucene.Net.Spatial.Prefix.AbstractPrefixTreeFilter: Denested BaseTermsEnumTraverser
    
    * BREAKING: Lucene.Net.Spatial.Prefix.Tree.GeohashPrefixTree.Factory: de-nested and renamed GeohashPrefixTreeFactory
    
    * Lucene.Net.Spatial.Prefix.Tree.QuadPrefixTree.Factory: de-nested and renamed QuadPrefixTreeFactory
    
    * BUG: Lucene.Net.Spatial.Query.SpatialArgs::ctor(): Set operation and shape fields rather than calling the virtual properties to set them (which can cause initialization issues)
    
    * Lucene.Net.Spatial.Query.SpatialArgsParser: Added missing guard clauses
    
    * Lucene.Net.Spatial.Prefix.Tree.Cell: Added guard clauses for both constructors and Reset() method
    
    * Lucene.Net.Spatial.Prefix.Tree.SpatialPrefixTree: Added guard clauses
    
    * Lucene.Net.Spatial.Prefix.Tree.GeohashPrefixTree: Added guard clauses
    
    * Lucene.Net.Spatial.Prefix.Tree.QuadPrefixTree: Added guard clauses
    
    * Lucene.Net.Spatial.Prefix.Tree.SpatialPrefixTreeFactory: Added guard clauses
    
    * Lucene.Net.Spatial.Prefix.AbstractPrefixTreeFilter: Added guard clauses, enabled nullable reference type support
    
    * Lucene.Net.Spatial.Prefix.Tree.Cell: Enabled nullable reference type support; fixed potential NullReferenceException in CompareTo() implementation
    
    * Lucene.Net.Spatial.Prefix.Tree.QuadPrefixTree: Enabled nullable reference type support
    
    * Lucene.Net.Spatial.Prefix.Tree.SpatialPrefixTree: Added nullable reference type support
    
    * Lucene.Net.Spatial.Prefix.Tree.SpatialPrefixTreeFactory: Enabled nullable reference type support. Added missing overload to MakeSPT() with an Assembly parameter for loading types from external assemblies.
    
    * Lucene.Net.Spatial.Prefix.AbstractVisitionPrefixTreeFilter: Added nullable reference type support and updated docs
    
    * BREAKING: Lucene.Net.Spatial.Prefix.AbstractVisitingPrefixTreeFilter: De-nested VisitorTemplate class and changed protected field m_prefixGridScanLevel to a public property named PrefixGridScanLevel.
    
    * Lucene.Net.Spatial.Prefix.ContainsPrefixTreeFilter: Enabled nullable reference type support
    
    * Lucene.Net.Spatial.Prefix.IntersectsPrefixTreeFilter: Added nullable reference type support
    
    * Lucene.Net.Spatial.Prefix.PointPrefixTreeFieldCacheProvider: Added nullable reference type support
    
    * Lucene.Net.Spatial.Prefix.PrefixTreeStrategy: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatial.Prefix.RecursivePrefixTreeStrategy: Enabled nullable reference type support
    
    * Lucene.Net.Spatial.Prefix.TermQueryPrefixTreeStrategy: Added nullable reference type support
    
    * Lucene.Net.Spatial.Prefix.WithinPrefixTreeFilter: Added nullable reference type support and guard clauses (including AbstractPrefixTreeFilter and AbstractVisitingPrevixTreeFilter constructors)
    
    * Lucene.Net.Spatial.Query.SpatialArgs: Added support for nullable reference types and added guard clauses
    
    * Lucene.Net.Spatial.Query.SpatialArgsParser: Added nullable reference type support
    
    * Lucene.Net.Spatail.Query.SpatialOperation: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatial.Query.UnsupportedSpatialOperation: Added nullable reference type support
    
    * Lucene.Net.Spatial.Serialized.SerializedDVStrategy: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatial.Util.CachingDoubleValueSource: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatial.Util.DistanceToShapeValueSource: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatial.Util.ShapeFieldCache: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatial.Util.ShapeFieldCacheDistanceValueSource: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatial.Util.ShapeFieldCacheProvider: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatial.Util.ShapePredicateValueSource: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatial.Util.ValueSourceFilter: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatial.Vector.DistanceValueSource: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatial.Vector.PointVectorStrategy: Added nullable reference type support and guard clauses. Changed SUFFIX_X and SUFFIX_Y to constants.
    
    * Lucene.Net.Spatial.DisjointSpatialFilter: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatial.SpatialStrategy: Added nullable reference type support and guard clauses
    
    * Lucene.Net.Spatiial.Prefix.Tree.GeohashPrefixTree: Added nullable reference type support and guard clauses
    
    * SWEEP: Lucene.Net.Spatial: Enabled nullable reference type support in the project, and removed declaration in individual files
    
    * BREAKING: Lucene.Net.Spatial.Query: Renamed UnsupportedSpatialOperation > UnsupportedSpatialOperationException to match .NET conventions
---
 .build/dependencies.props                          |   3 +-
 .../ByTask/Feeds/SpatialDocMaker.cs                |  11 +-
 .../ByTask/Feeds/SpatialFileQueryMaker.cs          |   4 +-
 .../Lucene.Net.Benchmark.csproj                    |   2 +-
 src/Lucene.Net.Spatial/DisjointSpatialFilter.cs    |  30 +-
 src/Lucene.Net.Spatial/Lucene.Net.Spatial.csproj   |   3 +-
 .../Prefix/AbstractPrefixTreeFilter.cs             | 108 ++--
 .../Prefix/AbstractVisitingPrefixTreeFilter.cs     | 699 +++++++++++----------
 .../Prefix/ContainsPrefixTreeFilter.cs             |  54 +-
 .../Prefix/IntersectsPrefixTreeFilter.cs           |  45 +-
 .../Prefix/PointPrefixTreeFieldCacheProvider.cs    |  18 +-
 .../Prefix/PrefixTreeStrategy.cs                   |  37 +-
 .../Prefix/RecursivePrefixTreeStrategy.cs          |  13 +-
 .../Prefix/TermQueryPrefixTreeStrategy.cs          |  17 +-
 src/Lucene.Net.Spatial/Prefix/Tree/Cell.cs         |  93 ++-
 .../Prefix/Tree/GeohashPrefixTree.cs               |  74 ++-
 .../Prefix/Tree/QuadPrefixTree.cs                  | 110 +++-
 .../Prefix/Tree/SpatialPrefixTree.cs               |  52 +-
 .../Prefix/Tree/SpatialPrefixTreeFactory.cs        |  61 +-
 .../Prefix/WithinPrefixTreeFilter.cs               | 101 +--
 src/Lucene.Net.Spatial/Query/SpatialArgs.cs        |  21 +-
 src/Lucene.Net.Spatial/Query/SpatialArgsParser.cs  |  58 +-
 src/Lucene.Net.Spatial/Query/SpatialOperation.cs   |  59 +-
 .../Query/UnsupportedSpatialOperation.cs           |  12 +-
 .../Serialized/SerializedDVStrategy.cs             |  65 +-
 src/Lucene.Net.Spatial/SpatialStrategy.cs          |  17 +-
 .../Util/CachingDoubleValueSource.cs               |  22 +-
 .../Util/DistanceToShapeValueSource.cs             |  28 +-
 src/Lucene.Net.Spatial/Util/ShapeFieldCache.cs     |  17 +-
 .../Util/ShapeFieldCacheDistanceValueSource.cs     |  44 +-
 .../Util/ShapeFieldCacheProvider.cs                |  31 +-
 .../Util/ShapePredicateValueSource.cs              |  39 +-
 src/Lucene.Net.Spatial/Util/ValueSourceFilter.cs   |  14 +-
 .../Vector/DistanceValueSource.cs                  |  27 +-
 .../Vector/PointVectorStrategy.cs                  |  46 +-
 .../ExceptionHandling/ExceptionScanningTestCase.cs |   2 +-
 .../DistanceStrategyTest.cs                        |   8 +-
 .../Lucene.Net.Tests.Spatial.csproj                |   3 +-
 src/Lucene.Net.Tests.Spatial/PortedSolr3Test.cs    |  10 +-
 .../Prefix/NtsPolygonTest.cs                       |  16 +-
 .../Prefix/SpatialOpRecursivePrefixTreeTest.cs     |  68 +-
 .../Prefix/TestRecursivePrefixTreeStrategy.cs      |  20 +-
 .../Prefix/TestTermQueryPrefixGridStrategy.cs      |  11 +-
 .../Prefix/Tree/SpatialPrefixTreeTest.cs           |   6 +-
 .../Query/SpatialArgsParserTest.cs                 |   6 +-
 .../QueryEqualsHashCodeTest.cs                     |   6 +-
 .../Serialized/SerializedStrategyTest.cs           |   4 +-
 src/Lucene.Net.Tests.Spatial/SpatialArgsTest.cs    |   6 +-
 src/Lucene.Net.Tests.Spatial/SpatialExample.cs     |  18 +-
 src/Lucene.Net.Tests.Spatial/SpatialTestCase.cs    |   6 +-
 src/Lucene.Net.Tests.Spatial/SpatialTestData.cs    |   6 +-
 src/Lucene.Net.Tests.Spatial/SpatialTestQuery.cs   |   2 +-
 src/Lucene.Net.Tests.Spatial/StrategyTestCase.cs   |   4 +-
 src/Lucene.Net.Tests.Spatial/TestTestFramework.cs  |   6 +-
 .../Vector/TestPointVectorStrategy.cs              |   6 +-
 55 files changed, 1333 insertions(+), 916 deletions(-)

diff --git a/.build/dependencies.props b/.build/dependencies.props
index 3245a77..98836d4 100644
--- a/.build/dependencies.props
+++ b/.build/dependencies.props
@@ -74,8 +74,7 @@
     <PrismCorePackageVersion>7.2.0.1422</PrismCorePackageVersion>
     <RandomizedTestingGeneratorsPackageVersion>2.7.8</RandomizedTestingGeneratorsPackageVersion>
     <SharpZipLibPackageVersion>1.1.0</SharpZipLibPackageVersion>
-    <Spatial4nCorePackageVersion>0.4.1</Spatial4nCorePackageVersion>
-    <Spatial4nCoreNTSPackageVersion>$(Spatial4nCorePackageVersion)</Spatial4nCoreNTSPackageVersion>
+    <Spatial4nPackageVersion>0.4.1.1</Spatial4nPackageVersion>
     <SystemNetPrimitivesPackageVersion>4.3.0</SystemNetPrimitivesPackageVersion>
     <SystemReflectionEmitPackageVersion>4.3.0</SystemReflectionEmitPackageVersion>
     <SystemReflectionEmitILGenerationPackageVersion>4.3.0</SystemReflectionEmitILGenerationPackageVersion>
diff --git a/src/Lucene.Net.Benchmark/ByTask/Feeds/SpatialDocMaker.cs b/src/Lucene.Net.Benchmark/ByTask/Feeds/SpatialDocMaker.cs
index fa0f407..c3c8f88 100644
--- a/src/Lucene.Net.Benchmark/ByTask/Feeds/SpatialDocMaker.cs
+++ b/src/Lucene.Net.Benchmark/ByTask/Feeds/SpatialDocMaker.cs
@@ -3,8 +3,8 @@ using Lucene.Net.Documents;
 using Lucene.Net.Spatial;
 using Lucene.Net.Spatial.Prefix;
 using Lucene.Net.Spatial.Prefix.Tree;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 using System.Collections;
 using System.Collections.Generic;
@@ -141,12 +141,7 @@ namespace Lucene.Net.Benchmarks.ByTask.Feeds
                                                       SpatialContext ctx)
         {
             //A factory for the prefix tree grid
-            // LUCENENET: The second argument was ClassLoader in Java, which should be made into
-            // Assembly in .NET. However, Spatial4n currently doesn't support it.
-            // In .NET it makes more logical sense to make 2 overloads and throw ArgumentNullException
-            // if the second argument is null, anyway. So no need to change this once support has been added.
-            // See: https://github.com/NightOwl888/Spatial4n/issues/1
-            SpatialPrefixTree grid = SpatialPrefixTreeFactory.MakeSPT(configMap/*, assembly: null*/, ctx);
+            SpatialPrefixTree grid = SpatialPrefixTreeFactory.MakeSPT(configMap, assembly: null, ctx);
 
             RecursivePrefixTreeStrategy strategy = new RecursivePrefixTreeStrategyAnonymousClass(grid, SPATIAL_FIELD, config);
 
diff --git a/src/Lucene.Net.Benchmark/ByTask/Feeds/SpatialFileQueryMaker.cs b/src/Lucene.Net.Benchmark/ByTask/Feeds/SpatialFileQueryMaker.cs
index a2e469e..bd80009 100644
--- a/src/Lucene.Net.Benchmark/ByTask/Feeds/SpatialFileQueryMaker.cs
+++ b/src/Lucene.Net.Benchmark/ByTask/Feeds/SpatialFileQueryMaker.cs
@@ -4,7 +4,7 @@ using Lucene.Net.Queries.Function;
 using Lucene.Net.Search;
 using Lucene.Net.Spatial;
 using Lucene.Net.Spatial.Queries;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
 using System.Collections.Generic;
 using JCG = J2N.Collections.Generic;
 
@@ -29,7 +29,7 @@ namespace Lucene.Net.Benchmarks.ByTask.Feeds
 
     /// <summary>
     /// Reads spatial data from the body field docs from an internally created <see cref="LineDocSource"/>.
-    /// It's parsed by <see cref="Spatial4n.Core.Context.SpatialContext.ReadShapeFromWkt(string)"/> and then
+    /// It's parsed by <see cref="Spatial4n.Context.SpatialContext.ReadShapeFromWkt(string)"/> and then
     /// further manipulated via a configurable <see cref="IShapeConverter"/>. When using point
     /// data, it's likely you'll want to configure the shape converter so that the query shapes actually
     /// cover a region. The queries are all created &amp; cached in advance. This query maker works in
diff --git a/src/Lucene.Net.Benchmark/Lucene.Net.Benchmark.csproj b/src/Lucene.Net.Benchmark/Lucene.Net.Benchmark.csproj
index 5ffa901..0b60a06 100644
--- a/src/Lucene.Net.Benchmark/Lucene.Net.Benchmark.csproj
+++ b/src/Lucene.Net.Benchmark/Lucene.Net.Benchmark.csproj
@@ -54,7 +54,7 @@
   <ItemGroup>
     <PackageReference Include="J2N" Version="$(J2NPackageVersion)" />
     <PackageReference Include="ICU4N.Collation" Version="$(ICU4NCollationPackageVersion)" />
-    <PackageReference Include="Spatial4n.Core" Version="$(Spatial4nCorePackageVersion)" />
+    <PackageReference Include="Spatial4n" Version="$(Spatial4nPackageVersion)" />
   </ItemGroup>
 
   <ItemGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
diff --git a/src/Lucene.Net.Spatial/DisjointSpatialFilter.cs b/src/Lucene.Net.Spatial/DisjointSpatialFilter.cs
index 9e12dfc..88156bc 100644
--- a/src/Lucene.Net.Spatial/DisjointSpatialFilter.cs
+++ b/src/Lucene.Net.Spatial/DisjointSpatialFilter.cs
@@ -1,4 +1,4 @@
-using Lucene.Net.Index;
+using Lucene.Net.Index;
 using Lucene.Net.Queries;
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Queries;
@@ -32,12 +32,12 @@ namespace Lucene.Net.Spatial
     /// A document is considered disjoint if it has spatial data that does not
     /// intersect with the query shape.  Another way of looking at this is that it's
     /// a way to invert a query shape.
-    /// 
+    /// <para/>
     /// @lucene.experimental
     /// </summary>
     public class DisjointSpatialFilter : Filter
     {
-        private readonly string field;//maybe null
+        private readonly string? field;//maybe null
         private readonly Filter intersectsFilter;
 
         /// <param name="strategy">Needed to compute intersects</param>
@@ -45,10 +45,16 @@ namespace Lucene.Net.Spatial
         /// <param name="field">
         /// This field is used to determine which docs have spatial data via
         /// <see cref="IFieldCache.GetDocsWithField(AtomicReader, string)"/>.
-        /// Passing null will assume all docs have spatial data.
+        /// Passing <c>null</c> will assume all docs have spatial data.
         /// </param>
-        public DisjointSpatialFilter(SpatialStrategy strategy, SpatialArgs args, string field)
+        public DisjointSpatialFilter(SpatialStrategy strategy, SpatialArgs args, string? field)
         {
+            // LUCENENET specific - added guard clauses
+            if (strategy is null)
+                throw new ArgumentNullException(nameof(strategy));
+            if (args is null)
+                throw new ArgumentNullException(nameof(args));
+
             this.field = field;
 
             // TODO consider making SpatialArgs cloneable
@@ -59,7 +65,7 @@ namespace Lucene.Net.Spatial
         }
 
         //restore so it looks like it was
-        public override bool Equals(object o)
+        public override bool Equals(object? o)
         {
             if (this == o)
             {
@@ -83,15 +89,19 @@ namespace Lucene.Net.Spatial
 
         public override int GetHashCode()
         {
-            int result = field != null ? field.GetHashCode() : 0;
+            int result = field is null ? 0 : field.GetHashCode();
             result = 31 * result + intersectsFilter.GetHashCode();
             return result;
         }
 
         /// <exception cref="IOException"></exception>
-        public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
+        public override DocIdSet? GetDocIdSet(AtomicReaderContext context, IBits? acceptDocs)
         {
-            IBits docsWithField;
+            // LUCENENET specific - added guard clause
+            if (context is null)
+                throw new ArgumentNullException(nameof(context));
+
+            IBits? docsWithField;
             if (field is null)
             {
                 docsWithField = null;
@@ -102,7 +112,7 @@ namespace Lucene.Net.Spatial
                 // which is nice but loading it in this way might be slower than say using an
                 // intersects filter against the world bounds. So do we add a method to the
                 // strategy, perhaps?  But the strategy can't cache it.
-                docsWithField = FieldCache.DEFAULT.GetDocsWithField((context.AtomicReader), field);
+                docsWithField = FieldCache.DEFAULT.GetDocsWithField(context.AtomicReader, field);
 
                 int maxDoc = context.AtomicReader.MaxDoc;
                 if (docsWithField.Length != maxDoc)
diff --git a/src/Lucene.Net.Spatial/Lucene.Net.Spatial.csproj b/src/Lucene.Net.Spatial/Lucene.Net.Spatial.csproj
index 9848aa4..00cb63f 100644
--- a/src/Lucene.Net.Spatial/Lucene.Net.Spatial.csproj
+++ b/src/Lucene.Net.Spatial/Lucene.Net.Spatial.csproj
@@ -36,6 +36,7 @@
     <PackageTags>$(PackageTags);spatial;geo;geospatial;2d</PackageTags>
     <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
     <NoWarn>$(NoWarn);1591;1573</NoWarn>
+    <Nullable>enable</Nullable>
   </PropertyGroup>
 
   
@@ -46,7 +47,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Spatial4n.Core" Version="$(Spatial4nCorePackageVersion)" /> 
+    <PackageReference Include="Spatial4n" Version="$(Spatial4nPackageVersion)" /> 
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/Lucene.Net.Spatial/Prefix/AbstractPrefixTreeFilter.cs b/src/Lucene.Net.Spatial/Prefix/AbstractPrefixTreeFilter.cs
index f49f841..40dbb43 100644
--- a/src/Lucene.Net.Spatial/Prefix/AbstractPrefixTreeFilter.cs
+++ b/src/Lucene.Net.Spatial/Prefix/AbstractPrefixTreeFilter.cs
@@ -1,11 +1,9 @@
-using Lucene.Net.Diagnostics;
-using Lucene.Net.Index;
+using Lucene.Net.Index;
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Util;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
 using System;
-using System.Diagnostics;
 
 namespace Lucene.Net.Spatial.Prefix
 {
@@ -39,18 +37,20 @@ namespace Lucene.Net.Spatial.Prefix
 
         protected AbstractPrefixTreeFilter(IShape queryShape, string fieldName, SpatialPrefixTree grid, int detailLevel) // LUCENENET: CA1012: Abstract types should not have constructors (marked protected)
         {
-            this.m_queryShape = queryShape;
-            this.m_fieldName = fieldName;
-            this.m_grid = grid;
+            // LUCENENET specific - added guard clauses
+            this.m_queryShape = queryShape ?? throw new ArgumentNullException(nameof(queryShape));
+            this.m_fieldName = fieldName ?? throw new ArgumentNullException(nameof(fieldName));
+            this.m_grid = grid ?? throw new ArgumentNullException(nameof(grid));
             this.m_detailLevel = detailLevel;
         }
 
-        public override bool Equals(object o)
+        public override bool Equals(object? o)
         {
             if (this == o)
             {
                 return true;
             }
+            if (o is null) return false; // LUCENENET specific: null check
             if (!GetType().Equals(o.GetType()))
             {
                 return false;
@@ -79,51 +79,7 @@ namespace Lucene.Net.Spatial.Prefix
             return result;
         }
 
-        #region Nested type: BaseTermsEnumTraverser
-
-        /// <summary>
-        /// Holds transient state and docid collecting utility methods as part of
-        /// traversing a <see cref="TermsEnum">Lucene.Net.Index.TermsEnum</see>.
-        /// </summary>
-        public abstract class BaseTermsEnumTraverser
-        {
-            protected readonly AbstractPrefixTreeFilter m_outerInstance;
-            protected readonly AtomicReaderContext m_context;
-            protected IBits m_acceptDocs;
-            protected readonly int m_maxDoc;
-
-            protected TermsEnum m_termsEnum;//remember to check for null in getDocIdSet
-            protected DocsEnum m_docsEnum;
-
-            protected BaseTermsEnumTraverser(AbstractPrefixTreeFilter outerInstance, AtomicReaderContext context, IBits acceptDocs) // LUCENENET: CA1012: Abstract types should not have constructors (marked protected)
-            {
-                this.m_outerInstance = outerInstance;
-                
-                this.m_context = context;
-                AtomicReader reader = context.AtomicReader;
-                this.m_acceptDocs = acceptDocs;
-                m_maxDoc = reader.MaxDoc;
-                Terms terms = reader.GetTerms(outerInstance.m_fieldName);
-                if (terms != null)
-                {
-                    m_termsEnum = terms.GetEnumerator();
-                }
-            }
-
-            protected virtual void CollectDocs(FixedBitSet bitSet)
-            {
-                //WARN: keep this specialization in sync
-                if (Debugging.AssertsEnabled) Debugging.Assert(m_termsEnum != null);
-                m_docsEnum = m_termsEnum.Docs(m_acceptDocs, m_docsEnum, DocsFlags.NONE);
-                int docid;
-                while ((docid = m_docsEnum.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
-                {
-                    bitSet.Set(docid);
-                }
-            }
-        }
-
-        #endregion
+        // LUCENENET specific - de-nested BaseTermsEnumTraverser
 
         /* Eventually uncomment when needed.
 
@@ -142,4 +98,50 @@ namespace Lucene.Net.Spatial.Prefix
         }
         */
     }
+
+    /// <summary>
+    /// Holds transient state and docid collecting utility methods as part of
+    /// traversing a <see cref="TermsEnum">Lucene.Net.Index.TermsEnum</see>.
+    /// </summary>
+    public abstract class BaseTermsEnumTraverser
+    {
+        protected readonly AbstractPrefixTreeFilter m_filter;
+        protected readonly AtomicReaderContext m_context;
+        protected IBits? m_acceptDocs;
+        protected readonly int m_maxDoc;
+
+        protected TermsEnum? m_termsEnum;//remember to check for null in getDocIdSet
+        protected DocsEnum? m_docsEnum;
+
+        protected BaseTermsEnumTraverser(AbstractPrefixTreeFilter filter, AtomicReaderContext context, IBits? acceptDocs) // LUCENENET: CA1012: Abstract types should not have constructors (marked protected)
+        {
+            // LUCENENET specific - added guard clauses
+            this.m_filter = filter ?? throw new ArgumentNullException(nameof(filter));
+            this.m_context = context ?? throw new ArgumentNullException(nameof(context));
+            AtomicReader reader = context.AtomicReader;
+            this.m_acceptDocs = acceptDocs;
+            m_maxDoc = reader.MaxDoc;
+            Terms? terms = reader.GetTerms(filter.m_fieldName);
+            if (terms != null)
+            {
+                m_termsEnum = terms.GetEnumerator();
+            }
+        }
+
+        protected virtual void CollectDocs(FixedBitSet bitSet)
+        {
+            // LUCENENET specific - use guard clause instead of assert
+            if (m_termsEnum is null)
+                throw new InvalidOperationException($"{nameof(m_termsEnum)} must not be null.");
+            if (bitSet is null)
+                throw new ArgumentNullException(nameof(bitSet));
+            //WARN: keep this specialization in sync
+            m_docsEnum = m_termsEnum.Docs(m_acceptDocs, m_docsEnum, DocsFlags.NONE);
+            int docid;
+            while ((docid = m_docsEnum.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
+            {
+                bitSet.Set(docid);
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/Lucene.Net.Spatial/Prefix/AbstractVisitingPrefixTreeFilter.cs b/src/Lucene.Net.Spatial/Prefix/AbstractVisitingPrefixTreeFilter.cs
index a500c8b..f403253 100644
--- a/src/Lucene.Net.Spatial/Prefix/AbstractVisitingPrefixTreeFilter.cs
+++ b/src/Lucene.Net.Spatial/Prefix/AbstractVisitingPrefixTreeFilter.cs
@@ -1,13 +1,12 @@
-using Lucene.Net.Diagnostics;
+using Lucene.Net.Diagnostics;
 using Lucene.Net.Index;
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Util;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
 using System;
 using System.Collections;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.IO;
 
 namespace Lucene.Net.Spatial.Prefix
@@ -37,7 +36,7 @@ namespace Lucene.Net.Spatial.Prefix
     /// Subclasses implement <see cref="Filter.GetDocIdSet(AtomicReaderContext, IBits)"/>
     /// by instantiating a custom <see cref="VisitorTemplate"/> subclass (i.e. an anonymous inner class) and implement the
     /// required methods.
-    /// 
+    /// <para/>
     /// @lucene.internal
     /// </summary>
     public abstract class AbstractVisitingPrefixTreeFilter : AbstractPrefixTreeFilter
@@ -45,431 +44,438 @@ namespace Lucene.Net.Spatial.Prefix
         // Historical note: this code resulted from a refactoring of RecursivePrefixTreeFilter,
         // which in turn came out of SOLR-2155
 
-        protected readonly int m_prefixGridScanLevel;//at least one less than grid.getMaxLevels()
+        public int PrefixGridScanLevel { get; }//at least one less than grid.getMaxLevels()
 
         protected AbstractVisitingPrefixTreeFilter(IShape queryShape, string fieldName, SpatialPrefixTree grid, 
                                                 int detailLevel, int prefixGridScanLevel) // LUCENENET: CA1012: Abstract types should not have constructors (marked protected)
             : base(queryShape, fieldName, grid, detailLevel)
         {
-            this.m_prefixGridScanLevel = Math.Max(0, Math.Min(prefixGridScanLevel, grid.MaxLevels - 1));
+            this.PrefixGridScanLevel = Math.Max(0, Math.Min(prefixGridScanLevel, grid.MaxLevels - 1));
             if (Debugging.AssertsEnabled) Debugging.Assert(detailLevel <= grid.MaxLevels);
         }
 
-        public override bool Equals(object o)
-        {
-            if (!base.Equals(o))
-            {
-                return false;//checks getClass == o.getClass & instanceof
-            }
+        // LUCENENET: Removed these because they are simply calling the base implementation
+        //public override bool Equals(object? o)
+        //{
+        //    if (!base.Equals(o))
+        //    {
+        //        return false;//checks getClass == o.getClass & instanceof
+        //    }
 
-            //Ignore prefixGridScanLevel as it is merely a tuning parameter.
+        //    //Ignore prefixGridScanLevel as it is merely a tuning parameter.
 
-            return true;
-        }
+        //    return true;
+        //}
 
-        public override int GetHashCode()
-        {
-            int result = base.GetHashCode();
-            return result;
-        }
+        //public override int GetHashCode()
+        //{
+        //    int result = base.GetHashCode();
+        //    return result;
+        //}
+    }
 
-        #region Nested type: VisitorTemplate
+    /// <summary>
+    /// An abstract class designed to make it easy to implement predicates or
+    /// other operations on a <see cref="SpatialPrefixTree"/> indexed field. An instance
+    /// of this class is not designed to be re-used across AtomicReaderContext
+    /// instances so simply create a new one for each call to, say a
+    /// <see cref="Filter.GetDocIdSet(AtomicReaderContext, IBits)"/>.
+    /// The <see cref="GetDocIdSet()"/> method here starts the work. It first checks
+    /// that there are indexed terms; if not it quickly returns null. Then it calls
+    /// <see cref="Start()">Start()</see> so a subclass can set up a return value, like an
+    /// <see cref="FixedBitSet"/>. Then it starts the traversal
+    /// process, calling <see cref="FindSubCellsToVisit(Cell)"/>
+    /// which by default finds the top cells that intersect <c>queryShape</c>. If
+    /// there isn't an indexed cell for a corresponding cell returned for this
+    /// method then it's short-circuited until it finds one, at which point
+    /// <see cref="Visit(Cell)"/> is called. At
+    /// some depths, of the tree, the algorithm switches to a scanning mode that
+    /// calls <see cref="VisitScanned(Cell)"/>
+    /// for each leaf cell found.
+    /// <para/>
+    /// @lucene.internal
+    /// </summary>
+    public abstract class VisitorTemplate : BaseTermsEnumTraverser
+    {
+        /* Future potential optimizations:
 
-        /// <summary>
-        /// An abstract class designed to make it easy to implement predicates or
-        /// other operations on a <see cref="SpatialPrefixTree"/> indexed field. An instance
-        /// of this class is not designed to be re-used across AtomicReaderContext
-        /// instances so simply create a new one for each call to, say a
-        /// <see cref="Lucene.Net.Search.Filter.GetDocIdSet(Lucene.Net.Index.AtomicReaderContext, Lucene.Net.Util.IBits)"/>.
-        /// The <see cref="GetDocIdSet()"/> method here starts the work. It first checks
-        /// that there are indexed terms; if not it quickly returns null. Then it calls
-        /// <see cref="Start()">Start()</see> so a subclass can set up a return value, like an
-        /// <see cref="Lucene.Net.Util.FixedBitSet"/>. Then it starts the traversal
-        /// process, calling <see cref="FindSubCellsToVisit(Lucene.Net.Spatial.Prefix.Tree.Cell)"/>
-        /// which by default finds the top cells that intersect <c>queryShape</c>. If
-        /// there isn't an indexed cell for a corresponding cell returned for this
-        /// method then it's short-circuited until it finds one, at which point
-        /// <see cref="Visit(Lucene.Net.Spatial.Prefix.Tree.Cell)"/> is called. At
-        /// some depths, of the tree, the algorithm switches to a scanning mode that
-        /// calls <see cref="VisitScanned(Lucene.Net.Spatial.Prefix.Tree.Cell)"/>
-        /// for each leaf cell found.
-        /// 
-        /// @lucene.internal
-        /// </summary>
-        public abstract class VisitorTemplate : BaseTermsEnumTraverser
-        {
-            /* Future potential optimizations:
+        * Can a polygon query shape be optimized / made-simpler at recursive depths
+            (e.g. intersection of shape + cell box)
 
-            * Can a polygon query shape be optimized / made-simpler at recursive depths
-              (e.g. intersection of shape + cell box)
+        * RE "scan" vs divide & conquer performance decision:
+            We should use termsEnum.docFreq() as an estimate on the number of places at
+            this depth.  It would be nice if termsEnum knew how many terms
+            start with the current term without having to repeatedly next() & test to find out.
 
-            * RE "scan" vs divide & conquer performance decision:
-              We should use termsEnum.docFreq() as an estimate on the number of places at
-              this depth.  It would be nice if termsEnum knew how many terms
-              start with the current term without having to repeatedly next() & test to find out.
+        * Perhaps don't do intermediate seek()'s to cells above detailLevel that have Intersects
+            relation because we won't be collecting those docs any way.  However seeking
+            does act as a short-circuit.  So maybe do some percent of the time or when the level
+            is above some threshold.
 
-            * Perhaps don't do intermediate seek()'s to cells above detailLevel that have Intersects
-              relation because we won't be collecting those docs any way.  However seeking
-              does act as a short-circuit.  So maybe do some percent of the time or when the level
-              is above some threshold.
+        * Each shape.relate(otherShape) result could be cached since much of the same relations
+            will be invoked when multiple segments are involved.
 
-            * Each shape.relate(otherShape) result could be cached since much of the same relations
-              will be invoked when multiple segments are involved.
+        */
 
-            */
+        protected readonly bool m_hasIndexedLeaves;//if false then we can skip looking for them
 
-            protected readonly bool m_hasIndexedLeaves;//if false then we can skip looking for them
+        private VNode? curVNode;//current pointer, derived from query shape
+        private readonly BytesRef curVNodeTerm = new BytesRef();//curVNode.cell's term. // LUCENENET: marked readonly
+        private Cell? scanCell;
 
-            private VNode curVNode;//current pointer, derived from query shape
-            private readonly BytesRef curVNodeTerm = new BytesRef();//curVNode.cell's term. // LUCENENET: marked readonly
-            private Cell scanCell;
+        private BytesRef? thisTerm; //the result of termsEnum.term()
 
-            private BytesRef thisTerm; //the result of termsEnum.term()
+        protected VisitorTemplate(AbstractVisitingPrefixTreeFilter outerInstance, AtomicReaderContext context, IBits? acceptDocs,
+                                bool hasIndexedLeaves) // LUCENENET: CA1012: Abstract types should not have constructors (marked protected)
+            : base(outerInstance, context, acceptDocs)
+        {
+            this.m_hasIndexedLeaves = hasIndexedLeaves;
+        }
 
-            protected VisitorTemplate(AbstractVisitingPrefixTreeFilter outerInstance, AtomicReaderContext context, IBits acceptDocs,
-                                   bool hasIndexedLeaves) // LUCENENET: CA1012: Abstract types should not have constructors (marked protected)
-                : base(outerInstance, context, acceptDocs)
+        public virtual DocIdSet? GetDocIdSet()
+        {
+            if (Debugging.AssertsEnabled) Debugging.Assert(curVNode is null, "Called more than once?");
+            if (m_termsEnum is null)
             {
-                this.m_hasIndexedLeaves = hasIndexedLeaves;
+                return null;
             }
-
-            public virtual DocIdSet GetDocIdSet()
+            //advance
+            if (!m_termsEnum.MoveNext())
             {
-                if (Debugging.AssertsEnabled) Debugging.Assert(curVNode is null, "Called more than once?");
-                if (m_termsEnum is null)
-                {
-                    return null;
-                }
-                //advance
-                if (!m_termsEnum.MoveNext())
-                {
-                    return null;// all done
-                }
-                thisTerm = m_termsEnum.Term;
+                return null;// all done
+            }
+            thisTerm = m_termsEnum.Term;
                 
-                curVNode = new VNode(null);
-                curVNode.Reset(m_outerInstance.m_grid.WorldCell);
+            curVNode = new VNode(null);
+            curVNode.Reset(m_filter.m_grid.WorldCell);
 
-                Start();
+            Start();
 
-                AddIntersectingChildren();
+            AddIntersectingChildren();
 
-                while (thisTerm != null)//terminates for other reasons too!
+            while (thisTerm != null)//terminates for other reasons too!
+            {
+                //Advance curVNode pointer
+                if (curVNode.children != null)
                 {
-                    //Advance curVNode pointer
-                    if (curVNode.children != null)
-                    {
-                        //-- HAVE CHILDREN: DESCEND
+                    //-- HAVE CHILDREN: DESCEND
 
-                        // LUCENENET NOTE: Must call this line before calling MoveNext()
-                        // on the enumerator.
+                    // LUCENENET NOTE: Must call this line before calling MoveNext()
+                    // on the enumerator.
 
-                        //if we put it there then it has something
-                        PreSiblings(curVNode);
+                    //if we put it there then it has something
+                    PreSiblings(curVNode);
 
-                        // LUCENENET IMPORTANT: Must not call this inline with Debug.Assert
-                        // because the compiler removes Debug.Assert statements in release mode!!
-                        bool hasNext = curVNode.children.MoveNext();
-                        if (Debugging.AssertsEnabled) Debugging.Assert(hasNext);
+                    // LUCENENET IMPORTANT: Must not call this inline with Debug.Assert
+                    // because the compiler removes Debug.Assert statements in release mode!!
+                    bool hasNext = curVNode.children.MoveNext();
+                    if (Debugging.AssertsEnabled) Debugging.Assert(hasNext);
 
-                        curVNode = curVNode.children.Current;
-                    }
-                    else
-                    {
-                        //-- NO CHILDREN: ADVANCE TO NEXT SIBLING
-                        VNode parentVNode = curVNode.parent;
-                        while (true)
-                        {
-                            if (parentVNode is null)
-                            {
-                                goto main_break;// all done
-                            }
-                            if (parentVNode.children.MoveNext())
-                            {
-                                //advance next sibling
-                                curVNode = parentVNode.children.Current;
-                                break;
-                            }
-                            else
-                            {
-                                //reached end of siblings; pop up
-                                PostSiblings(parentVNode);
-                                parentVNode.children = null;
-                                //GC
-                                parentVNode = parentVNode.parent;
-                            }
-                        }
-                    }
-                    //Seek to curVNode's cell (or skip if termsEnum has moved beyond)
-                    curVNodeTerm.Bytes = curVNode.cell.GetTokenBytes();
-                    curVNodeTerm.Length = curVNodeTerm.Bytes.Length;
-                    int compare = m_termsEnum.Comparer.Compare(thisTerm, curVNodeTerm);
-                    if (compare > 0)
-                    {
-                        // leap frog (termsEnum is beyond where we would otherwise seek)
-                        if (Debugging.AssertsEnabled) Debugging.Assert(!m_context.AtomicReader.GetTerms(m_outerInstance.m_fieldName).GetEnumerator().SeekExact(curVNodeTerm), "should be absent");
-                    }
-                    else
+                    curVNode = curVNode.children.Current;
+                }
+                else
+                {
+                    //-- NO CHILDREN: ADVANCE TO NEXT SIBLING
+                    VNode? parentVNode = curVNode.parent;
+                    while (true)
                     {
-                        if (compare < 0)
+                        if (parentVNode is null)
                         {
-                            // Seek !
-                            TermsEnum.SeekStatus seekStatus = m_termsEnum.SeekCeil(curVNodeTerm);
-                            if (seekStatus == TermsEnum.SeekStatus.END)
-                            {
-                                break;// all done
-                            }
-                            thisTerm = m_termsEnum.Term;
-                            if (seekStatus == TermsEnum.SeekStatus.NOT_FOUND)
-                            {
-                                continue; // leap frog
-                            }
+                            goto main_break;// all done
                         }
-                        // Visit!
-                        bool descend = Visit(curVNode.cell);
-                        //advance
-                        if (!m_termsEnum.MoveNext())
+                        if (parentVNode.children!.MoveNext())
                         {
-                            thisTerm = null;
-                            break;// all done
+                            //advance next sibling
+                            curVNode = parentVNode.children.Current;
+                            break;
                         }
-                        thisTerm = m_termsEnum.Term;
-                        if (descend)
+                        else
                         {
-                            AddIntersectingChildren();
+                            //reached end of siblings; pop up
+                            PostSiblings(parentVNode);
+                            parentVNode.children = null;
+                            //GC
+                            parentVNode = parentVNode.parent;
                         }
                     }
-                    ;
-                }//main loop
-                main_break: { }
-                
-                return Finish();
-            }
-
-            /// <summary>
-            /// Called initially, and whenever <see cref="Visit(Lucene.Net.Spatial.Prefix.Tree.Cell)"/>
-            /// returns true.
-            /// </summary>
-            /// <exception cref="IOException"></exception>
-            private void AddIntersectingChildren()
-            {
-                if (Debugging.AssertsEnabled) Debugging.Assert(thisTerm != null);
-                Cell cell = curVNode.cell;
-                if (cell.Level >= m_outerInstance.m_detailLevel)
+                }
+                //Seek to curVNode's cell (or skip if termsEnum has moved beyond)
+                curVNodeTerm.Bytes = curVNode.cell!.GetTokenBytes();
+                curVNodeTerm.Length = curVNodeTerm.Bytes.Length;
+                int compare = m_termsEnum.Comparer.Compare(thisTerm, curVNodeTerm);
+                if (compare > 0)
                 {
-                    throw IllegalStateException.Create("Spatial logic error");
+                    // leap frog (termsEnum is beyond where we would otherwise seek)
+                    if (Debugging.AssertsEnabled) Debugging.Assert(!m_context.AtomicReader.GetTerms(m_filter.m_fieldName).GetEnumerator().SeekExact(curVNodeTerm), "should be absent");
                 }
-                //Check for adjacent leaf (happens for indexed non-point shapes)
-                if (m_hasIndexedLeaves && cell.Level != 0)
+                else
                 {
-                    //If the next indexed term just adds a leaf marker ('+') to cell,
-                    // then add all of those docs
-                    if (Debugging.AssertsEnabled) Debugging.Assert(StringHelper.StartsWith(thisTerm, curVNodeTerm));//TODO refactor to use method on curVNode.cell
-                    scanCell = m_outerInstance.m_grid.GetCell(thisTerm.Bytes, thisTerm.Offset, thisTerm.Length, scanCell);
-                    if (scanCell.Level == cell.Level && scanCell.IsLeaf)
+                    if (compare < 0)
                     {
-                        VisitLeaf(scanCell);
-                        //advance
-                        if (!m_termsEnum.MoveNext())
+                        // Seek !
+                        TermsEnum.SeekStatus seekStatus = m_termsEnum.SeekCeil(curVNodeTerm);
+                        if (seekStatus == TermsEnum.SeekStatus.END)
                         {
-                            return;// all done
+                            break;// all done
                         }
                         thisTerm = m_termsEnum.Term;
+                        if (seekStatus == TermsEnum.SeekStatus.NOT_FOUND)
+                        {
+                            continue; // leap frog
+                        }
                     }
-                }
-                
-                //Decide whether to continue to divide & conquer, or whether it's time to
-                // scan through terms beneath this cell.
-                // Scanning is a performance optimization trade-off.
-
-                //TODO use termsEnum.docFreq() as heuristic
-                bool scan = cell.Level >= ((AbstractVisitingPrefixTreeFilter)m_outerInstance).m_prefixGridScanLevel;//simple heuristic
-
-                if (!scan)
-                {
-                    //Divide & conquer (ultimately termsEnum.seek())
-
-                    IEnumerator<Cell> subCellsIter = FindSubCellsToVisit(cell);
-                    if (!subCellsIter.MoveNext())
+                    // Visit!
+                    bool descend = Visit(curVNode.cell);
+                    //advance
+                    if (!m_termsEnum.MoveNext())
                     {
-                        return;//not expected
+                        thisTerm = null;
+                        break;// all done
+                    }
+                    thisTerm = m_termsEnum.Term;
+                    if (descend)
+                    {
+                        AddIntersectingChildren();
                     }
-                    curVNode.children = new VNodeCellIterator(subCellsIter, new VNode(curVNode));
                 }
-                else
-                {
-                    //Scan (loop of termsEnum.next())
+                ;
+            }//main loop
+            main_break: { }
+                
+            return Finish();
+        }
 
-                    Scan(m_outerInstance.m_detailLevel);
-                }
-            }
+        /// <summary>
+        /// Called initially, and whenever <see cref="Visit(Cell)"/>
+        /// returns true.
+        /// </summary>
+        /// <exception cref="IOException"></exception>
+        private void AddIntersectingChildren()
+        {
+            // LUCENENET specific - Use guard clause instead of assert
+            if (thisTerm is null)
+                throw new InvalidOperationException($"{nameof(thisTerm)} must not be null when calling AddIntersectingChildren().");
 
-            /// <summary>
-            /// Called when doing a divide &amp; conquer to find the next intersecting cells
-            /// of the query shape that are beneath <paramref name="cell"/>. <paramref name="cell"/> is
-            /// guaranteed to have an intersection and thus this must return some number
-            /// of nodes.
-            /// </summary>
-            protected internal virtual IEnumerator<Cell> FindSubCellsToVisit(Cell cell)
+            Cell cell = curVNode!.cell!;
+            if (cell.Level >= m_filter.m_detailLevel)
             {
-                return cell.GetSubCells(m_outerInstance.m_queryShape).GetEnumerator();
+                throw IllegalStateException.Create("Spatial logic error");
             }
-
-            /// <summary>
-            /// Scans (<c>termsEnum.MoveNext()</c>) terms until a term is found that does
-            /// not start with curVNode's cell. If it finds a leaf cell or a cell at
-            /// level <paramref name="scanDetailLevel"/> then it calls
-            /// <see cref="VisitScanned(Lucene.Net.Spatial.Prefix.Tree.Cell)"/>.
-            /// </summary>
-            /// <exception cref="IOException"></exception>
-            protected internal virtual void Scan(int scanDetailLevel)
+            //Check for adjacent leaf (happens for indexed non-point shapes)
+            if (m_hasIndexedLeaves && cell.Level != 0)
             {
-                // LUCENENET specific - on the first loop, we need to check for null,
-                // but on each subsequent loop, we can use the result of MoveNext()
-                if (!(thisTerm is null) && StringHelper.StartsWith(thisTerm, curVNodeTerm)) //TODO refactor to use method on curVNode.cell
+                //If the next indexed term just adds a leaf marker ('+') to cell,
+                // then add all of those docs
+                if (Debugging.AssertsEnabled) Debugging.Assert(StringHelper.StartsWith(thisTerm, curVNodeTerm));//TODO refactor to use method on curVNode.cell
+                scanCell = m_filter.m_grid.GetCell(thisTerm.Bytes, thisTerm.Offset, thisTerm.Length, scanCell);
+                if (scanCell.Level == cell.Level && scanCell.IsLeaf)
                 {
-                    bool moved;
-                    do
+                    VisitLeaf(scanCell);
+                    //advance
+                    if (!m_termsEnum!.MoveNext())
                     {
-                        scanCell = m_outerInstance.m_grid.GetCell(thisTerm.Bytes, thisTerm.Offset, thisTerm.Length, scanCell);
-
-                        int termLevel = scanCell.Level;
-                        if (termLevel < scanDetailLevel)
-                        {
-                            if (scanCell.IsLeaf)
-                                VisitScanned(scanCell);
-                        }
-                        else if (termLevel == scanDetailLevel)
-                        {
-                            if (!scanCell.IsLeaf)//LUCENE-5529
-                                VisitScanned(scanCell);
-                        }
-
-                    } while ((moved = m_termsEnum.MoveNext()) && StringHelper.StartsWith(thisTerm = m_termsEnum.Term, curVNodeTerm));
-
-                    // LUCENENET: Enusure we set thisTerm to null if the iteration ends
-                    if (!moved)
-                        thisTerm = null;
+                        return;// all done
+                    }
+                    thisTerm = m_termsEnum.Term;
                 }
             }
+                
+            //Decide whether to continue to divide & conquer, or whether it's time to
+            // scan through terms beneath this cell.
+            // Scanning is a performance optimization trade-off.
 
-            #region Nested type: VNodeCellIterator
+            //TODO use termsEnum.docFreq() as heuristic
+            bool scan = cell.Level >= ((AbstractVisitingPrefixTreeFilter)m_filter).PrefixGridScanLevel;//simple heuristic
 
-            /// <summary>
-            /// Used for <see cref="VNode.children"/>.
-            /// </summary>
-            private class VNodeCellIterator : IEnumerator<VNode>
+            if (!scan)
             {
-                internal readonly IEnumerator<Cell> cellIter;
-                private readonly VNode vNode;
-                private bool first = true;
+                //Divide & conquer (ultimately termsEnum.seek())
 
-                internal VNodeCellIterator(IEnumerator<Cell> cellIter, VNode vNode)
+                IEnumerator<Cell> subCellsIter = FindSubCellsToVisit(cell);
+                if (!subCellsIter.MoveNext())
                 {
-                    //term loop
-                    this.cellIter = cellIter;
-                    this.vNode = vNode;
+                    return;//not expected
                 }
+                curVNode.children = new VNodeCellEnumerator(subCellsIter, new VNode(curVNode));
+            }
+            else
+            {
+                //Scan (loop of termsEnum.next())
 
-                //it always removes
+                Scan(m_filter.m_detailLevel);
+            }
+        }
 
-                #region IEnumerator<VNode> Members
+        /// <summary>
+        /// Called when doing a divide &amp; conquer to find the next intersecting cells
+        /// of the query shape that are beneath <paramref name="cell"/>. <paramref name="cell"/> is
+        /// guaranteed to have an intersection and thus this must return some number
+        /// of nodes.
+        /// </summary>
+        /// <exception cref="ArgumentNullException"><paramref name="cell"/> is <c>null</c>.</exception>
+        protected virtual IEnumerator<Cell> FindSubCellsToVisit(Cell cell)
+        {
+            if (cell is null)
+                throw new ArgumentNullException(nameof(cell));
 
-                public void Dispose()
-                {
-                    cellIter.Dispose();
-                }
+            return cell.GetSubCells(m_filter.m_queryShape).GetEnumerator();
+        }
 
-                public bool MoveNext()
+        /// <summary>
+        /// Scans (<c>termsEnum.MoveNext()</c>) terms until a term is found that does
+        /// not start with curVNode's cell. If it finds a leaf cell or a cell at
+        /// level <paramref name="scanDetailLevel"/> then it calls
+        /// <see cref="VisitScanned(Cell)"/>.
+        /// </summary>
+        /// <exception cref="IOException"></exception>
+        protected internal virtual void Scan(int scanDetailLevel)
+        {
+            // LUCENENET specific - on the first loop, we need to check for null,
+            // but on each subsequent loop, we can use the result of MoveNext()
+            if (!(thisTerm is null) && StringHelper.StartsWith(thisTerm, curVNodeTerm)) //TODO refactor to use method on curVNode.cell
+            {
+                // LUCENENET specific - added guard clause for m_termsEnum
+                if (m_termsEnum is null)
+                    throw new InvalidOperationException($"{nameof(m_termsEnum)} must be set prior to calling Scan(int).");
+
+                bool moved;
+                do
                 {
-                    //if (Debugging.AssertsEnabled) Debugging.Assert(cellIter.Current != null);
-
-                    // LUCENENET NOTE: The consumer of this class calls
-                    // cellIter.MoveNext() before it is instantiated.
-                    // So, the first call here
-                    // to MoveNext() must not move the cursor.
-                    bool result;
-                    if (!first)
+                    scanCell = m_filter.m_grid.GetCell(thisTerm.Bytes, thisTerm.Offset, thisTerm.Length, scanCell);
+
+                    int termLevel = scanCell.Level;
+                    if (termLevel < scanDetailLevel)
                     {
-                        result = cellIter.MoveNext();
+                        if (scanCell.IsLeaf)
+                            VisitScanned(scanCell);
                     }
-                    else
+                    else if (termLevel == scanDetailLevel)
                     {
-                        result = true;
-                        first = false;
+                        if (!scanCell.IsLeaf)//LUCENE-5529
+                            VisitScanned(scanCell);
                     }
 
-                    // LUCENENET NOTE: Need to skip this call
-                    // if there are no more results because null
-                    // is not allowed
-                    if (result == true)
-                    {
-                        vNode.Reset(cellIter.Current);
-                    }
-                    return result;
-                }
+                } while ((moved = m_termsEnum.MoveNext()) && StringHelper.StartsWith(thisTerm = m_termsEnum.Term, curVNodeTerm));
 
-                public void Reset()
-                {
-                    cellIter.Reset();
-                }
+                // LUCENENET: Enusure we set thisTerm to null if the iteration ends
+                if (!moved)
+                    thisTerm = null;
+            }
+        }
 
-                public VNode Current => vNode;
+        #region Nested type: VNodeCellEnumerator
 
-                object IEnumerator.Current => Current;
+        /// <summary>
+        /// Used for <see cref="VNode.children"/>.
+        /// </summary>
+        private class VNodeCellEnumerator : IEnumerator<VNode>
+        {
+            internal readonly IEnumerator<Cell> cellIter;
+            private readonly VNode vNode;
+            private bool first = true;
 
-                #endregion
+            internal VNodeCellEnumerator(IEnumerator<Cell> cellIter, VNode vNode)
+            {
+                //term loop
+                this.cellIter = cellIter;
+                this.vNode = vNode;
             }
 
-            #endregion
-
-            /// <summary>Called first to setup things.</summary>
-            /// <exception cref="IOException"></exception>
-            protected internal abstract void Start();
-
-            /// <summary>Called last to return the result.</summary>
-            /// <exception cref="IOException"></exception>
-            protected internal abstract DocIdSet Finish();
-
-            /// <summary>
-            /// Visit an indexed cell returned from
-            /// <see cref="FindSubCellsToVisit(Lucene.Net.Spatial.Prefix.Tree.Cell)"/>.
-            /// </summary>
-            /// <param name="cell">An intersecting cell.</param>
-            /// <returns>
-            /// true to descend to more levels. It is an error to return true
-            /// if cell.Level == detailLevel
-            /// </returns>
-            /// <exception cref="IOException"></exception>
-            protected internal abstract bool Visit(Cell cell);
-
-            /// <summary>Called after visit() returns true and an indexed leaf cell is found.</summary>
-            /// <remarks>
-            /// Called after Visit() returns true and an indexed leaf cell is found. An
-            /// indexed leaf cell means associated documents generally won't be found at
-            /// further detail levels.
-            /// </remarks>
-            /// <exception cref="IOException"></exception>
-            protected internal abstract void VisitLeaf(Cell cell);
-
-            /// <summary>
-            /// The cell is either indexed as a leaf or is the last level of detail. It
-            /// might not even intersect the query shape, so be sure to check for that.
-            /// </summary>
-            /// <exception cref="IOException"></exception>
-            protected internal abstract void VisitScanned(Cell cell);
-
-            protected internal virtual void PreSiblings(VNode vNode)
+            //it always removes
+
+            #region IEnumerator<VNode> Members
+
+            public void Dispose()
             {
+                cellIter.Dispose();
             }
 
-            protected internal virtual void PostSiblings(VNode vNode)
+            public bool MoveNext()
             {
+                //if (Debugging.AssertsEnabled) Debugging.Assert(cellIter.Current != null);
+
+                // LUCENENET NOTE: The consumer of this class calls
+                // cellIter.MoveNext() before it is instantiated.
+                // So, the first call here
+                // to MoveNext() must not move the cursor.
+                bool result;
+                if (!first)
+                {
+                    result = cellIter.MoveNext();
+                }
+                else
+                {
+                    result = true;
+                    first = false;
+                }
+
+                // LUCENENET NOTE: Need to skip this call
+                // if there are no more results because null
+                // is not allowed
+                if (result == true)
+                {
+                    vNode.Reset(cellIter.Current);
+                }
+                return result;
+            }
+
+            public void Reset()
+            {
+                cellIter.Reset();
             }
-            //class VisitorTemplate
+
+            public VNode Current => vNode;
+
+            object IEnumerator.Current => Current;
+
+            #endregion IEnumerator<VNode> Members
         }
 
-        #endregion
+        #endregion Nested type: VNodeCellEnumerator
+
+        /// <summary>Called first to setup things.</summary>
+        /// <exception cref="IOException"></exception>
+        protected abstract void Start();
+
+        /// <summary>Called last to return the result.</summary>
+        /// <exception cref="IOException"></exception>
+        protected abstract DocIdSet Finish();
+
+        /// <summary>
+        /// Visit an indexed cell returned from
+        /// <see cref="FindSubCellsToVisit(Cell)"/>.
+        /// </summary>
+        /// <param name="cell">An intersecting cell.</param>
+        /// <returns>
+        /// true to descend to more levels. It is an error to return true
+        /// if cell.Level == detailLevel
+        /// </returns>
+        /// <exception cref="IOException"></exception>
+        protected abstract bool Visit(Cell cell);
+
+        /// <summary>Called after <see cref="Visit(Cell)"/> returns <c>true</c> and an indexed leaf cell is found.</summary>
+        /// <remarks>
+        /// Called after <see cref="Visit(Cell)"/> returns <c>true</c> and an indexed leaf cell is found. An
+        /// indexed leaf cell means associated documents generally won't be found at
+        /// further detail levels.
+        /// </remarks>
+        /// <exception cref="IOException"></exception>
+        protected abstract void VisitLeaf(Cell cell);
+
+        /// <summary>
+        /// The cell is either indexed as a leaf or is the last level of detail. It
+        /// might not even intersect the query shape, so be sure to check for that.
+        /// </summary>
+        /// <exception cref="IOException"></exception>
+        protected abstract void VisitScanned(Cell cell);
+
+        protected virtual void PreSiblings(VNode vNode)
+        {
+        }
+
+        protected virtual void PostSiblings(VNode vNode)
+        {
+        }
 
         #region Nested type: VNode
 
@@ -477,22 +483,22 @@ namespace Lucene.Net.Spatial.Prefix
         /// A Visitor node/cell found via the query shape for <see cref="VisitorTemplate"/>.
         /// Sometimes these are reset(cell). It's like a LinkedList node but forms a
         /// tree.
-        /// 
+        /// <para/>
         /// @lucene.internal
         /// </summary>
-        public class VNode
+        protected class VNode
         {
             //Note: The VNode tree adds more code to debug/maintain v.s. a flattened
             // LinkedList that we used to have. There is more opportunity here for
             // custom behavior (see preSiblings & postSiblings) but that's not
             // leveraged yet. Maybe this is slightly more GC friendly.
 
-            internal readonly VNode parent;//only null at the root
-            internal IEnumerator<VNode> children;//null, then sometimes set, then null
-            internal Cell cell;//not null (except initially before reset())
+            internal readonly VNode? parent;//only null at the root
+            internal IEnumerator<VNode>? children;//null, then sometimes set, then null
+            internal Cell? cell;//not null (except initially before reset())
 
             /// <summary>Call <see cref="Reset(Cell)"/> after to set the cell.</summary>
-            internal VNode(VNode parent)
+            internal VNode(VNode? parent)
             {
                 // remember to call reset(cell) after
                 this.parent = parent;
@@ -506,6 +512,7 @@ namespace Lucene.Net.Spatial.Prefix
             }
         }
 
-        #endregion
-    }
+        #endregion Nested type: VNode
+
+    } //class VisitorTemplate
 }
\ No newline at end of file
diff --git a/src/Lucene.Net.Spatial/Prefix/ContainsPrefixTreeFilter.cs b/src/Lucene.Net.Spatial/Prefix/ContainsPrefixTreeFilter.cs
index 6790384..f28376a 100644
--- a/src/Lucene.Net.Spatial/Prefix/ContainsPrefixTreeFilter.cs
+++ b/src/Lucene.Net.Spatial/Prefix/ContainsPrefixTreeFilter.cs
@@ -1,12 +1,11 @@
-using Lucene.Net.Diagnostics;
+using Lucene.Net.Diagnostics;
 using Lucene.Net.Index;
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Util;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.IO;
 
 namespace Lucene.Net.Spatial.Prefix
@@ -54,7 +53,7 @@ namespace Lucene.Net.Spatial.Prefix
             this.m_multiOverlappingIndexedShapes = multiOverlappingIndexedShapes;
         }
 
-        public override bool Equals(object o)
+        public override bool Equals(object? o)
         {
             if (!base.Equals(o))
                 return false;
@@ -66,7 +65,7 @@ namespace Lucene.Net.Spatial.Prefix
             return base.GetHashCode() + (m_multiOverlappingIndexedShapes ? 1 : 0);
         }
 
-        public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
+        public override DocIdSet? GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
         {
             return new ContainsVisitor(this, context, acceptDocs).Visit(m_grid.WorldCell, acceptDocs);
         }
@@ -79,11 +78,11 @@ namespace Lucene.Net.Spatial.Prefix
             }
 
             internal BytesRef termBytes = new BytesRef();
-            internal Cell nextCell;//see getLeafDocs
+            internal Cell? nextCell;//see getLeafDocs
 
             /// <remarks>This is the primary algorithm; recursive.  Returns null if finds none.</remarks>
             /// <exception cref="IOException"></exception>
-            internal SmallDocSet Visit(Cell cell, IBits acceptContains)
+            internal SmallDocSet? Visit(Cell cell, IBits acceptContains)
             {
                 if (m_termsEnum is null)
                 {
@@ -91,18 +90,18 @@ namespace Lucene.Net.Spatial.Prefix
                     return null;
                 }
 
-                ContainsPrefixTreeFilter outerInstance = (ContainsPrefixTreeFilter)base.m_outerInstance;
+                ContainsPrefixTreeFilter outerInstance = (ContainsPrefixTreeFilter)base.m_filter;
 
                 //Leaf docs match all query shape
-                SmallDocSet leafDocs = GetLeafDocs(cell, acceptContains);
+                SmallDocSet? leafDocs = GetLeafDocs(cell, acceptContains);
                 // Get the AND of all child results (into combinedSubResults)
-                SmallDocSet combinedSubResults = null;
+                SmallDocSet? combinedSubResults = null;
                 //   Optimization: use null subCellsFilter when we know cell is within the query shape.
-                IShape subCellsFilter = outerInstance.m_queryShape;
-                if (cell.Level != 0 && ((cell.ShapeRel == SpatialRelation.NOT_SET || cell.ShapeRel == SpatialRelation.WITHIN)))
+                IShape? subCellsFilter = outerInstance.m_queryShape;
+                if (cell.Level != 0 && ((cell.ShapeRel == SpatialRelation.None || cell.ShapeRel == SpatialRelation.Within)))
                 {
                     subCellsFilter = null;
-                    if (Debugging.AssertsEnabled) Debugging.Assert(cell.Shape.Relate(outerInstance.m_queryShape) == SpatialRelation.WITHIN);
+                    if (Debugging.AssertsEnabled) Debugging.Assert(cell.Shape.Relate(outerInstance.m_queryShape) == SpatialRelation.Within);
                 }
                 ICollection<Cell> subCells = cell.GetSubCells(subCellsFilter);
                 foreach (Cell subCell in subCells)
@@ -116,7 +115,7 @@ namespace Lucene.Net.Spatial.Prefix
                         combinedSubResults = GetDocs(subCell, acceptContains);
                     }
                     else if (!outerInstance.m_multiOverlappingIndexedShapes && 
-                        subCell.ShapeRel == SpatialRelation.WITHIN)
+                        subCell.ShapeRel == SpatialRelation.Within)
                     {
                         combinedSubResults = GetLeafDocs(subCell, acceptContains); //recursion
                     }
@@ -155,15 +154,15 @@ namespace Lucene.Net.Spatial.Prefix
                 return this.m_termsEnum.SeekExact(termBytes);
             }
 
-            private SmallDocSet GetDocs(Cell cell, IBits acceptContains)
+            private SmallDocSet? GetDocs(Cell cell, IBits acceptContains)
             {
                 if (Debugging.AssertsEnabled) Debugging.Assert(new BytesRef(cell.GetTokenBytes()).Equals(termBytes));
                 return this.CollectDocs(acceptContains);
             }
 
-            private Cell lastLeaf = null;//just for assertion
+            private Cell? lastLeaf = null;//just for assertion
 
-            private SmallDocSet GetLeafDocs(Cell leafCell, IBits acceptContains)
+            private SmallDocSet? GetLeafDocs(Cell leafCell, IBits acceptContains)
             {
                 if (Debugging.AssertsEnabled)
                 {
@@ -180,7 +179,7 @@ namespace Lucene.Net.Spatial.Prefix
                     return null;
                 }
                 BytesRef nextTerm = m_termsEnum.Term;
-                nextCell = m_outerInstance.m_grid.GetCell(nextTerm.Bytes, nextTerm.Offset, nextTerm.Length, this.nextCell);
+                nextCell = m_filter.m_grid.GetCell(nextTerm.Bytes, nextTerm.Offset, nextTerm.Length, this.nextCell);
                 if (nextCell.Level == leafCell.Level && nextCell.IsLeaf)
                 {
                     return CollectDocs(acceptContains);
@@ -191,9 +190,16 @@ namespace Lucene.Net.Spatial.Prefix
                 }
             }
 
-            private SmallDocSet CollectDocs(IBits acceptContains)
+            private SmallDocSet? CollectDocs(IBits acceptContains)
             {
-                SmallDocSet set = null;
+                // LUCENENET specific - guard against null m_termsEnum
+                if (m_termsEnum is null)
+                {
+                    //signals all done
+                    return null;
+                }
+
+                SmallDocSet? set = null;
 
                 m_docsEnum = m_termsEnum.Docs(acceptContains, m_docsEnum, DocsFlags.NONE);
                 int docid;
@@ -253,8 +259,12 @@ namespace Lucene.Net.Spatial.Prefix
             public virtual int Count => intSet.Count;
 
             /// <summary>NOTE: modifies and returns either "this" or "other"</summary>
+            /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
             public virtual SmallDocSet Union(SmallDocSet other)
             {
+                if (other is null)
+                    throw new ArgumentNullException(nameof(other));
+
                 SmallDocSet bigger;
                 SmallDocSet smaller;
                 if (other.intSet.Count > this.intSet.Count)
@@ -279,12 +289,12 @@ namespace Lucene.Net.Spatial.Prefix
                 return bigger;
             }
 
-            public override IBits Bits =>
+            public override IBits? Bits =>
                 //if the # of docids is super small, return null since iteration is going
                 // to be faster
                 Count > 4 ? this : null;
 
-            public override DocIdSetIterator GetIterator()
+            public override DocIdSetIterator? GetIterator()
             {
                 if (Count == 0)
                 {
diff --git a/src/Lucene.Net.Spatial/Prefix/IntersectsPrefixTreeFilter.cs b/src/Lucene.Net.Spatial/Prefix/IntersectsPrefixTreeFilter.cs
index f9672c3..c5ecf29 100644
--- a/src/Lucene.Net.Spatial/Prefix/IntersectsPrefixTreeFilter.cs
+++ b/src/Lucene.Net.Spatial/Prefix/IntersectsPrefixTreeFilter.cs
@@ -1,8 +1,9 @@
-using Lucene.Net.Index;
+using Lucene.Net.Index;
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Util;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
+using System;
 using System.IO;
 
 namespace Lucene.Net.Spatial.Prefix
@@ -42,7 +43,7 @@ namespace Lucene.Net.Spatial.Prefix
             this.hasIndexedLeaves = hasIndexedLeaves;
         }
 
-        public override bool Equals(object o)
+        public override bool Equals(object? o)
         {
             return base.Equals(o) && hasIndexedLeaves == ((IntersectsPrefixTreeFilter)o).hasIndexedLeaves;
         }
@@ -60,7 +61,7 @@ namespace Lucene.Net.Spatial.Prefix
         }
 
         /// <exception cref="IOException"></exception>
-        public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
+        public override DocIdSet? GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
         {
             return new VisitorTemplateAnonymousClass(this, context, acceptDocs, hasIndexedLeaves).GetDocIdSet();
         }
@@ -69,43 +70,55 @@ namespace Lucene.Net.Spatial.Prefix
 
         private sealed class VisitorTemplateAnonymousClass : VisitorTemplate
         {
-            private FixedBitSet results;
+            private FixedBitSet? results;
 
             public VisitorTemplateAnonymousClass(IntersectsPrefixTreeFilter outerInstance, AtomicReaderContext context, IBits acceptDocs, bool hasIndexedLeaves)
                 : base(outerInstance, context, acceptDocs, hasIndexedLeaves)
             {
             }
 
-            protected internal override void Start()
+            protected override void Start()
             {
                 results = new FixedBitSet(m_maxDoc);
             }
 
-            protected internal override DocIdSet Finish()
+            protected override DocIdSet Finish()
             {
-                return results;
+                return results!;
             }
 
-            protected internal override bool Visit(Cell cell)
+            protected override bool Visit(Cell cell)
             {
-                if (cell.ShapeRel == SpatialRelation.WITHIN || cell.Level == m_outerInstance.m_detailLevel)
+                // LUCENENET specific - added guard clause
+                if (cell is null)
+                    throw new ArgumentNullException(nameof(cell));
+
+                if (cell.ShapeRel == SpatialRelation.Within || cell.Level == m_filter.m_detailLevel)
                 {
-                    CollectDocs(results);
+                    CollectDocs(results!);
                     return false;
                 }
                 return true;
             }
 
-            protected internal override void VisitLeaf(Cell cell)
+            protected override void VisitLeaf(Cell cell)
             {
-                CollectDocs(results);
+                // LUCENENET specific - added guard clause
+                if (cell is null)
+                    throw new ArgumentNullException(nameof(cell));
+
+                CollectDocs(results!);
             }
 
-            protected internal override void VisitScanned(Cell cell)
+            protected override void VisitScanned(Cell cell)
             {
-                if (m_outerInstance.m_queryShape.Relate(cell.Shape).Intersects())
+                // LUCENENET specific - added guard clause
+                if (cell is null)
+                    throw new ArgumentNullException(nameof(cell));
+
+                if (m_filter.m_queryShape.Relate(cell.Shape).Intersects())
                 {
-                    CollectDocs(results);
+                    CollectDocs(results!);
                 }
             }
         }
diff --git a/src/Lucene.Net.Spatial/Prefix/PointPrefixTreeFieldCacheProvider.cs b/src/Lucene.Net.Spatial/Prefix/PointPrefixTreeFieldCacheProvider.cs
index d7cf8bd..e2e4a65 100644
--- a/src/Lucene.Net.Spatial/Prefix/PointPrefixTreeFieldCacheProvider.cs
+++ b/src/Lucene.Net.Spatial/Prefix/PointPrefixTreeFieldCacheProvider.cs
@@ -1,7 +1,9 @@
-using Lucene.Net.Spatial.Prefix.Tree;
+using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Spatial.Util;
 using Lucene.Net.Util;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
+using System;
+using System.Diagnostics.CodeAnalysis;
 
 namespace Lucene.Net.Spatial.Prefix
 {
@@ -23,7 +25,7 @@ namespace Lucene.Net.Spatial.Prefix
      */
 
     /// <summary>
-    /// Implementation of <see cref="Lucene.Net.Spatial.Util.ShapeFieldCacheProvider{T}"/>
+    /// Implementation of <see cref="ShapeFieldCacheProvider{T}"/>
     /// designed for <see cref="PrefixTreeStrategy">PrefixTreeStrategy</see>s.
     /// 
     /// Note, due to the fragmented representation of Shapes in these Strategies, this implementation
@@ -33,17 +35,19 @@ namespace Lucene.Net.Spatial.Prefix
     /// </summary>
     public class PointPrefixTreeFieldCacheProvider : ShapeFieldCacheProvider<IPoint>
     {
-        internal readonly SpatialPrefixTree grid; //
+        private readonly SpatialPrefixTree grid; //
 
         public PointPrefixTreeFieldCacheProvider(SpatialPrefixTree grid, string shapeField, int defaultSize)
             : base(shapeField, defaultSize)
         {
-            this.grid = grid;
+            // LUCENENT specific - added guard clause
+            this.grid = grid ?? throw new ArgumentNullException(nameof(grid));
         }
 
-        private Cell scanCell = null;//re-used in readShape to save GC
+        private Cell? scanCell = null;//re-used in readShape to save GC
 
-        protected internal override IPoint ReadShape(BytesRef term)
+        [return: MaybeNull]
+        protected override IPoint ReadShape(BytesRef term)
         {
             scanCell = grid.GetCell(term.Bytes, term.Offset, term.Length, scanCell);
             if (scanCell.Level == grid.MaxLevels && !scanCell.IsLeaf)
diff --git a/src/Lucene.Net.Spatial/Prefix/PrefixTreeStrategy.cs b/src/Lucene.Net.Spatial/Prefix/PrefixTreeStrategy.cs
index a8d6b5c..c01a743 100644
--- a/src/Lucene.Net.Spatial/Prefix/PrefixTreeStrategy.cs
+++ b/src/Lucene.Net.Spatial/Prefix/PrefixTreeStrategy.cs
@@ -6,7 +6,7 @@ using Lucene.Net.Queries.Function;
 using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Spatial.Queries;
 using Lucene.Net.Spatial.Util;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
@@ -31,7 +31,7 @@ namespace Lucene.Net.Spatial.Prefix
      */
 
     /// <summary>
-    /// An abstract SpatialStrategy based on <see cref="Lucene.Net.Spatial.Prefix.Tree.SpatialPrefixTree"/>. The two
+    /// An abstract SpatialStrategy based on <see cref="SpatialPrefixTree"/>. The two
     /// subclasses are <see cref="RecursivePrefixTreeStrategy">RecursivePrefixTreeStrategy</see> and
     /// <see cref="TermQueryPrefixTreeStrategy">TermQueryPrefixTreeStrategy</see>.  This strategy is most effective as a fast
     /// approximate spatial search filter.
@@ -62,13 +62,13 @@ namespace Lucene.Net.Spatial.Prefix
     /// </list>
     /// 
     /// <h4>Implementation:</h4>
-    /// The <see cref="Lucene.Net.Spatial.Prefix.Tree.SpatialPrefixTree"/>
+    /// The <see cref="SpatialPrefixTree"/>
     /// does most of the work, for example returning
     /// a list of terms representing grids of various sizes for a supplied shape.
     /// An important
     /// configuration item is <see cref="DistErrPct"/> which balances
     /// shape precision against scalability.  See those docs.
-    /// 
+    /// <para/>
     /// @lucene.internal
     /// </summary>
     public abstract class PrefixTreeStrategy : SpatialStrategy
@@ -84,7 +84,8 @@ namespace Lucene.Net.Spatial.Prefix
         protected double m_distErrPct = SpatialArgs.DEFAULT_DISTERRPCT;// [ 0 TO 0.5 ]
 
         protected PrefixTreeStrategy(SpatialPrefixTree grid, string fieldName, bool simplifyIndexedCells) // LUCENENET: CA1012: Abstract types should not have constructors (marked protected)
-            : base(grid.SpatialContext, fieldName)
+            // LUCENENET specific - added guard clause
+            : base(grid is null ? throw new ArgumentNullException(nameof(grid)) : grid.SpatialContext, fieldName)
         {
             this.m_grid = grid;
             this.m_simplifyIndexedCells = simplifyIndexedCells;
@@ -109,13 +110,13 @@ namespace Lucene.Net.Spatial.Prefix
         /// <remarks>
         /// The default measure of shape precision affecting shapes at index and query
         /// times. Points don't use this as they are always indexed at the configured
-        /// maximum precision (<see cref="Lucene.Net.Spatial.Prefix.Tree.SpatialPrefixTree.MaxLevels"/>);
+        /// maximum precision (<see cref="SpatialPrefixTree.MaxLevels"/>);
         /// this applies to all other shapes. Specific shapes at index and query time
         /// can use something different than this default value.  If you don't set a
         /// default then the default is <see cref="SpatialArgs.DEFAULT_DISTERRPCT"/> --
         /// 2.5%.
         /// </remarks>
-        /// <seealso cref="Lucene.Net.Spatial.Queries.SpatialArgs.DistErrPct"/>
+        /// <seealso cref="SpatialArgs.DistErrPct"/>
         public virtual double DistErrPct
         {
             get => m_distErrPct;
@@ -124,11 +125,15 @@ namespace Lucene.Net.Spatial.Prefix
 
         public override Field[] CreateIndexableFields(IShape shape)
         {
+            // LUCENENET: Added guard clause
+            if (shape is null)
+                throw new ArgumentNullException(nameof(shape));
+
             double distErr = SpatialArgs.CalcDistanceFromErrPct(shape, m_distErrPct, m_ctx);
             return CreateIndexableFields(shape, distErr);
         }
 
-        public virtual Field[] CreateIndexableFields(IShape shape, double distErr)
+        public virtual Field[] CreateIndexableFields(IShape? shape, double distErr)
         {
             int detailLevel = m_grid.GetLevelForDistance(distErr);
             IList<Cell> cells = m_grid.GetCells(shape, detailLevel, true, m_simplifyIndexedCells);//intermediates cells
@@ -157,15 +162,16 @@ namespace Lucene.Net.Spatial.Prefix
         {
             private readonly ICharTermAttribute termAtt;
 
-            private IEnumerator<Cell> iter = null;
+            private readonly IEnumerator<Cell> iter; // LUCENENET specific - marked readonly and got rid of null setting
 
             public CellTokenStream(IEnumerator<Cell> tokens)
             {
-                this.iter = tokens;
+                // LUCENENET specific - added guard clause
+                this.iter = tokens ?? throw new ArgumentNullException(nameof(tokens));
                 termAtt = AddAttribute<ICharTermAttribute>();
             }
 
-            internal string nextTokenStringNeedingLeaf = null;
+            internal string? nextTokenStringNeedingLeaf = null;
 
             public override bool IncrementToken()
             {
@@ -205,8 +211,7 @@ namespace Lucene.Net.Spatial.Prefix
                 {
                     if (disposing)
                     {
-                        iter?.Dispose(); // LUCENENET specific - dispose iter and set to null
-                        iter = null;
+                        iter.Dispose(); // LUCENENET specific - dispose iter
                     }
                 }
                 finally
@@ -218,11 +223,15 @@ namespace Lucene.Net.Spatial.Prefix
 
         public override ValueSource MakeDistanceValueSource(IPoint queryPoint, double multiplier)
         {
+            // LUCENENET specific - added guard clause
+            if (queryPoint is null)
+                throw new ArgumentNullException(nameof(queryPoint));
+
             // LUCENENET specific - use Lazy<T> to make the create operation atomic. See #417.
             var p = provider.GetOrAdd(FieldName,
                 f => new Lazy<PointPrefixTreeFieldCacheProvider>(
                     () => new PointPrefixTreeFieldCacheProvider(m_grid, FieldName, m_defaultFieldValuesArrayLen)));
-            return new ShapeFieldCacheDistanceValueSource(m_ctx, p.Value, queryPoint, multiplier);
+            return new ShapeFieldCacheDistanceValueSource(m_ctx, p.Value!, queryPoint, multiplier);
         }
 
         public virtual SpatialPrefixTree Grid => m_grid;
diff --git a/src/Lucene.Net.Spatial/Prefix/RecursivePrefixTreeStrategy.cs b/src/Lucene.Net.Spatial/Prefix/RecursivePrefixTreeStrategy.cs
index f804a1a..7db25f9 100644
--- a/src/Lucene.Net.Spatial/Prefix/RecursivePrefixTreeStrategy.cs
+++ b/src/Lucene.Net.Spatial/Prefix/RecursivePrefixTreeStrategy.cs
@@ -1,7 +1,8 @@
-using Lucene.Net.Search;
+using Lucene.Net.Search;
 using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Spatial.Queries;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
+using System;
 
 namespace Lucene.Net.Spatial.Prefix
 {
@@ -28,7 +29,7 @@ namespace Lucene.Net.Spatial.Prefix
     /// Even a query shape with distErrPct=0 (fully precise to the grid) should have
     /// good performance for typical data, unless there is a lot of indexed data
     /// coincident with the shape's edge.
-    /// 
+    /// <para/>
     /// @lucene.experimental
     /// </summary>
     public class RecursivePrefixTreeStrategy : PrefixTreeStrategy
@@ -71,6 +72,10 @@ namespace Lucene.Net.Spatial.Prefix
 
         public override Filter MakeFilter(SpatialArgs args)
         {
+            // LUCENENET specific - added guard clause
+            if (args is null)
+                throw new ArgumentNullException(nameof(args));
+
             SpatialOperation op = args.Operation;
             if (op == SpatialOperation.IsDisjointTo)
             {
@@ -96,7 +101,7 @@ namespace Lucene.Net.Spatial.Prefix
                 return new ContainsPrefixTreeFilter(shape, FieldName, m_grid, detailLevel, 
                     m_multiOverlappingIndexedShapes);
             }
-            throw new UnsupportedSpatialOperation(op);
+            throw new UnsupportedSpatialOperationException(op);
         }
     }
 }
\ No newline at end of file
diff --git a/src/Lucene.Net.Spatial/Prefix/TermQueryPrefixTreeStrategy.cs b/src/Lucene.Net.Spatial/Prefix/TermQueryPrefixTreeStrategy.cs
index b2ca3d5..81e3c30 100644
--- a/src/Lucene.Net.Spatial/Prefix/TermQueryPrefixTreeStrategy.cs
+++ b/src/Lucene.Net.Spatial/Prefix/TermQueryPrefixTreeStrategy.cs
@@ -1,9 +1,10 @@
-using Lucene.Net.Queries;
+using Lucene.Net.Queries;
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Spatial.Queries;
 using Lucene.Net.Util;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
+using System;
 using System.Collections.Generic;
 
 namespace Lucene.Net.Spatial.Prefix
@@ -27,14 +28,14 @@ namespace Lucene.Net.Spatial.Prefix
 
     /// <summary>
     /// A basic implementation of <see cref="PrefixTreeStrategy"/> using a large
-    /// <see cref="Lucene.Net.Queries.TermsFilter"/> of all the cells from
-    /// <see cref="Lucene.Net.Spatial.Prefix.Tree.SpatialPrefixTree.GetCells(IShape, int, bool, bool)"/>. 
+    /// <see cref="TermsFilter"/> of all the cells from
+    /// <see cref="SpatialPrefixTree.GetCells(IShape, int, bool, bool)"/>. 
     /// It only supports the search of indexed Point shapes.
     /// <para/>
     /// The precision of query shapes (DistErrPct) is an important factor in using
     /// this Strategy. If the precision is too precise then it will result in many
     /// terms which will amount to a slower query.
-    /// 
+    /// <para/>
     /// @lucene.experimental
     /// </summary>
     public class TermQueryPrefixTreeStrategy : PrefixTreeStrategy
@@ -46,10 +47,14 @@ namespace Lucene.Net.Spatial.Prefix
 
         public override Filter MakeFilter(SpatialArgs args)
         {
+            // LUCENENET specific - added guard clause
+            if (args is null)
+                throw new ArgumentNullException(nameof(args));
+
             SpatialOperation op = args.Operation;
             if (op != SpatialOperation.Intersects)
             {
-                throw new UnsupportedSpatialOperation(op);
+                throw new UnsupportedSpatialOperationException(op);
             }
             IShape shape = args.Shape;
             int detailLevel = m_grid.GetLevelForDistance(args.ResolveDistErr(m_ctx, m_distErrPct));
diff --git a/src/Lucene.Net.Spatial/Prefix/Tree/Cell.cs b/src/Lucene.Net.Spatial/Prefix/Tree/Cell.cs
index 8ed811d..8bf141d 100644
--- a/src/Lucene.Net.Spatial/Prefix/Tree/Cell.cs
+++ b/src/Lucene.Net.Spatial/Prefix/Tree/Cell.cs
@@ -1,5 +1,5 @@
 using Lucene.Net.Diagnostics;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
@@ -40,7 +40,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         /// So we need to move the reference here and also set it before running the normal constructor
         /// logic.
         /// </summary>
-        protected readonly SpatialPrefixTree m_outerInstance;
+        protected readonly SpatialPrefixTree m_spatialPrefixTree;
 
 
         public const byte LEAF_BYTE = (byte)('+');//NOTE: must sort before letters & numbers
@@ -49,11 +49,11 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         /// Holds a byte[] and/or String representation of the cell. Both are lazy constructed from the other.
         /// Neither contains the trailing leaf byte.
         /// </summary>
-        private byte[] bytes;
+        private byte[]? bytes;
         private int b_off;
         private int b_len;
 
-        private string token;//this is the only part of equality
+        private string? token;//this is the only part of equality
 
         /// <summary>
         /// When set via <see cref="GetSubCells(IShape)">GetSubCells(filter)</see>, it is the relationship between this cell
@@ -69,15 +69,23 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         /// </remarks>
         protected bool m_leaf;
 
-        protected Cell(SpatialPrefixTree outerInstance, string token)
+        /// <summary>
+        /// Initializes a new instance of <see cref="Cell"/> for the
+        /// <paramref name="spatialPrefixTree"/> with the specified <paramref name="token"/>.
+        /// </summary>
+        /// <param name="spatialPrefixTree">The <see cref="SpatialPrefixTree"/> this instance belongs to.</param>
+        /// <param name="token">The string representation of this <see cref="Cell"/>.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="spatialPrefixTree"/> or <paramref name="token"/> is <c>null</c>.</exception>
+        protected Cell(SpatialPrefixTree spatialPrefixTree, string token)
         {
             // LUCENENET specific - set the outer instance here
             // because overrides of Shape may require it
-            this.m_outerInstance = outerInstance;
+            // LUCENENET specific - added guard clauses
+            this.m_spatialPrefixTree = spatialPrefixTree ?? throw new ArgumentNullException(nameof(spatialPrefixTree));
 
             //NOTE: must sort before letters & numbers
             //this is the only part of equality
-            this.token = token;
+            this.token = token ?? throw new ArgumentNullException(nameof(token));
             if (token.Length > 0 && token[token.Length - 1] == (char)LEAF_BYTE)
             {
                 this.token = token.Substring(0, (token.Length - 1) - 0);
@@ -89,34 +97,64 @@ namespace Lucene.Net.Spatial.Prefix.Tree
             }
         }
 
-        protected internal Cell(SpatialPrefixTree outerInstance, byte[] bytes, int off, int len)
+        /// <summary>
+        /// Initializes a new instance of <see cref="Cell"/> for the
+        /// <paramref name="spatialPrefixTree"/> with the specified <paramref name="bytes"/>, <paramref name="offset"/> and <paramref name="length"/>.
+        /// </summary>
+        /// <param name="spatialPrefixTree">The <see cref="SpatialPrefixTree"/> this instance belongs to.</param>
+        /// <param name="bytes">The representation of this <see cref="Cell"/>.</param>
+        /// <param name="offset">The offset into <paramref name="bytes"/> to use.</param>
+        /// <param name="length">The count of <paramref name="bytes"/> to use.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="spatialPrefixTree"/> or <paramref name="bytes"/> is <c>null</c>.</exception>
+        protected Cell(SpatialPrefixTree spatialPrefixTree, byte[] bytes, int offset, int length)
         {
             // LUCENENET specific - set the outer instance here
             // because overrides of Shape may require it
-            this.m_outerInstance = outerInstance;
+            this.m_spatialPrefixTree = spatialPrefixTree ?? throw new ArgumentNullException(nameof(spatialPrefixTree));
 
             //ensure any lazy instantiation completes to make this threadsafe
-            this.bytes = bytes;
-            b_off = off;
-            b_len = len;
+            this.bytes = bytes ?? throw new ArgumentNullException(nameof(bytes));
+
+            // LUCENENET specific - check that the offset and length are in bounds
+            int len = bytes.Length;
+            if (offset < 0)
+                throw new ArgumentOutOfRangeException(nameof(offset));
+            if (length < 0)
+                throw new ArgumentOutOfRangeException(nameof(length));
+            if (offset + length > len)
+                throw new ArgumentException($"{nameof(offset)} and {nameof(length)} must refer to a location within the array.");
+
+            b_off = offset;
+            b_len = length;
             B_fixLeaf();
         }
 
-        public virtual void Reset(byte[] bytes, int off, int len)
+        public virtual void Reset(byte[] bytes, int offset, int length)
         {
+            // LUCENENET specific - added guard clauses
+            if (bytes is null)
+                throw new ArgumentNullException(nameof(bytes));
+            int len = bytes.Length;
+            if (offset < 0)
+                throw new ArgumentOutOfRangeException(nameof(offset));
+            if (length < 0)
+                throw new ArgumentOutOfRangeException(nameof(length));
+            if (offset + length > len)
+                throw new ArgumentOutOfRangeException(nameof(length), "Offset and length must refer to a location within the array.");
+
             if (Debugging.AssertsEnabled) Debugging.Assert(Level != 0);
             token = null;
-            m_shapeRel = SpatialRelation.NOT_SET;
+            m_shapeRel = SpatialRelation.None;
             this.bytes = bytes;
-            b_off = off;
-            b_len = len;
+            b_off = offset;
+            b_len = length;
             B_fixLeaf();
         }
 
         private void B_fixLeaf()
         {
             //note that non-point shapes always have the maxLevels cell set with setLeaf
-            if (bytes[b_off + b_len - 1] == LEAF_BYTE)
+            if (bytes![b_off + b_len - 1] == LEAF_BYTE)
             {
                 b_len--;
                 SetLeaf();
@@ -151,7 +189,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
             get
             {
                 if (token is null)
-                    token = Encoding.UTF8.GetString(bytes, b_off, b_len);
+                    token = Encoding.UTF8.GetString(bytes!, b_off, b_len);
                 return token;
             }
         }
@@ -168,7 +206,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
             }
             else
             {
-                bytes = Encoding.UTF8.GetBytes(token);
+                bytes = Encoding.UTF8.GetBytes(token!);
                 b_off = 0;
                 b_len = bytes.Length;
             }
@@ -191,13 +229,13 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         /// </summary>
         /// <param name="shapeFilter">an optional filter for the returned cells.</param>
         /// <returns>A set of cells (no dups), sorted. Not Modifiable.</returns>
-        public virtual ICollection<Cell> GetSubCells(IShape shapeFilter)
+        public virtual ICollection<Cell> GetSubCells(IShape? shapeFilter)
         {
             //Note: Higher-performing subclasses might override to consider the shape filter to generate fewer cells.
             if (shapeFilter is IPoint point)
             {
                 Cell subCell = GetSubCell(point);
-                subCell.m_shapeRel = SpatialRelation.CONTAINS;
+                subCell.m_shapeRel = SpatialRelation.Contains;
                 return new ReadOnlyCollection<Cell>(new[] { subCell });
             }
 
@@ -211,12 +249,12 @@ namespace Lucene.Net.Spatial.Prefix.Tree
             foreach (Cell cell in cells)
             {
                 SpatialRelation rel = cell.Shape.Relate(shapeFilter);
-                if (rel == SpatialRelation.DISJOINT)
+                if (rel == SpatialRelation.Disjoint)
                 {
                     continue;
                 }
                 cell.m_shapeRel = rel;
-                if (rel == SpatialRelation.WITHIN)
+                if (rel == SpatialRelation.Within)
                 {
                     cell.SetLeaf();
                 }
@@ -258,19 +296,20 @@ namespace Lucene.Net.Spatial.Prefix.Tree
 
         #region IComparable<Cell> Members
 
-        public virtual int CompareTo(Cell o)
+        public virtual int CompareTo(Cell? other)
         {
-            return string.CompareOrdinal(TokenString, o.TokenString);
+            if (other is null) return 1; // LUCENENET specific - match other comparers
+            return string.CompareOrdinal(TokenString, other.TokenString);
         }
 
         #endregion
 
         #region Equality overrides
 
-        public override bool Equals(object obj)
+        public override bool Equals(object? obj)
         {
             return !(obj is null || !(obj is Cell cell)) &&
-                   TokenString.Equals(cell.TokenString, StringComparison.Ordinal);
+                TokenString.Equals(cell.TokenString, StringComparison.Ordinal);
         }
 
         public override int GetHashCode()
diff --git a/src/Lucene.Net.Spatial/Prefix/Tree/GeohashPrefixTree.cs b/src/Lucene.Net.Spatial/Prefix/Tree/GeohashPrefixTree.cs
index b9e1ce7..9ababdd 100644
--- a/src/Lucene.Net.Spatial/Prefix/Tree/GeohashPrefixTree.cs
+++ b/src/Lucene.Net.Spatial/Prefix/Tree/GeohashPrefixTree.cs
@@ -1,6 +1,6 @@
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
-using Spatial4n.Core.Util;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
+using Spatial4n.Util;
 using System;
 using System.Collections.Generic;
 using JCG = J2N.Collections.Generic;
@@ -33,28 +33,16 @@ namespace Lucene.Net.Spatial.Prefix.Tree
     /// </summary>
     public class GeohashPrefixTree : SpatialPrefixTree
     {
-        #region Nested type: Factory
+        // LUCENENET specific - de-nested Factory and renamed GeohashPrefixTreeFactory
 
         /// <summary>
-        /// Factory for creating <see cref="GeohashPrefixTree"/>
-        /// instances with useful defaults
+        /// Initializes a new instance of <see cref="GeohashPrefixTree"/> with the specified
+        /// spatial context (<paramref name="ctx"/>) and <paramref name="maxLevels"/>.
         /// </summary>
-        public class Factory : SpatialPrefixTreeFactory
-        {
-            protected internal override int GetLevelForDistance(double degrees)
-            {
-                var grid = new GeohashPrefixTree(m_ctx, GeohashPrefixTree.MaxLevelsPossible);
-                return grid.GetLevelForDistance(degrees);
-            }
-
-            protected internal override SpatialPrefixTree NewSPT()
-            {
-                return new GeohashPrefixTree(m_ctx, m_maxLevels ?? GeohashPrefixTree.MaxLevelsPossible);
-            }
-        }
-
-        #endregion
-
+        /// <param name="ctx">The spatial context.</param>
+        /// <param name="maxLevels">The maximum number of levels in the tree.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="ctx"/> is <c>null</c>.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="maxLevels"/> is less than or equal to 0 or greater than <see cref="MaxLevelsPossible"/>.</exception>
         public GeohashPrefixTree(SpatialContext ctx, int maxLevels)
             : base(ctx, maxLevels)
         {
@@ -71,7 +59,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         }
 
         /// <summary>Any more than this and there's no point (double lat &amp; lon are the same).</summary>
-        public static int MaxLevelsPossible => GeohashUtils.MAX_PRECISION;
+        public static int MaxLevelsPossible => GeohashUtils.MaxPrecision;
 
         public override int GetLevelForDistance(double dist)
         {
@@ -86,6 +74,10 @@ namespace Lucene.Net.Spatial.Prefix.Tree
 
         protected internal override Cell GetCell(IPoint p, int level)
         {
+            // LUCENENET specific - added guard clause
+            if (p is null)
+                throw new ArgumentNullException(nameof(p));
+
             return new GhCell(this, GeohashUtils.EncodeLatLon(p.Y, p.X, level));
         }
 
@@ -126,7 +118,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
                 IList<Cell> cells = new JCG.List<Cell>(hashes.Length);
                 foreach (string hash in hashes)
                 {
-                    cells.Add(new GhCell((GeohashPrefixTree)m_outerInstance, hash));
+                    cells.Add(new GhCell((GeohashPrefixTree)m_spatialPrefixTree, hash));
                 }
                 return cells;
             }
@@ -135,10 +127,10 @@ namespace Lucene.Net.Spatial.Prefix.Tree
 
             public override Cell GetSubCell(IPoint p)
             {
-                return m_outerInstance.GetCell(p, Level + 1);//not performant!
+                return m_spatialPrefixTree.GetCell(p, Level + 1);//not performant!
             }
 
-            private IShape shape;//cache
+            private IShape? shape;//cache
 
             public override IShape Shape
             {
@@ -146,13 +138,13 @@ namespace Lucene.Net.Spatial.Prefix.Tree
                 {
                     if (shape is null)
                     {
-                        shape = GeohashUtils.DecodeBoundary(Geohash, m_outerInstance.m_ctx);
+                        shape = GeohashUtils.DecodeBoundary(Geohash, m_spatialPrefixTree.m_ctx);
                     }
                     return shape;
                 }
             }
 
-            public override IPoint Center => GeohashUtils.Decode(Geohash, m_outerInstance.m_ctx);
+            public override IPoint Center => GeohashUtils.Decode(Geohash, m_spatialPrefixTree.m_ctx);
 
             private string Geohash => TokenString;
 
@@ -161,4 +153,30 @@ namespace Lucene.Net.Spatial.Prefix.Tree
 
         #endregion
     }
+
+    /// <summary>
+    /// Factory for creating <see cref="GeohashPrefixTree"/>
+    /// instances with useful defaults
+    /// </summary>
+    public class GeohashPrefixTreeFactory : SpatialPrefixTreeFactory
+    {
+        protected internal override int GetLevelForDistance(double degrees)
+        {
+            // LUCENENET specific - added guard clause
+            if (m_ctx is null)
+                throw new InvalidOperationException($"{nameof(m_ctx)} must be set prior to calling GetLevelForDistance(double).");
+
+            var grid = new GeohashPrefixTree(m_ctx, GeohashPrefixTree.MaxLevelsPossible);
+            return grid.GetLevelForDistance(degrees);
+        }
+
+        protected internal override SpatialPrefixTree NewSPT()
+        {
+            // LUCENENET specific - added guard clause
+            if (m_ctx is null)
+                throw new InvalidOperationException($"{nameof(m_ctx)} must be set prior to calling NewSPT().");
+
+            return new GeohashPrefixTree(m_ctx, m_maxLevels ?? GeohashPrefixTree.MaxLevelsPossible);
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/Lucene.Net.Spatial/Prefix/Tree/QuadPrefixTree.cs b/src/Lucene.Net.Spatial/Prefix/Tree/QuadPrefixTree.cs
index 0032c1c..1ebc3bc 100644
--- a/src/Lucene.Net.Spatial/Prefix/Tree/QuadPrefixTree.cs
+++ b/src/Lucene.Net.Spatial/Prefix/Tree/QuadPrefixTree.cs
@@ -1,6 +1,7 @@
 using Lucene.Net.Diagnostics;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
@@ -29,31 +30,12 @@ namespace Lucene.Net.Spatial.Prefix.Tree
     /// A <see cref="SpatialPrefixTree"/> which uses a
     /// <a href="http://en.wikipedia.org/wiki/Quadtree">quad tree</a> in which an
     /// indexed term will be generated for each cell, 'A', 'B', 'C', 'D'.
-    /// 
+    /// <para/>
     /// @lucene.experimental
     /// </summary>
     public class QuadPrefixTree : SpatialPrefixTree
     {
-        #region Nested type: Factory
-
-        /// <summary>
-        /// Factory for creating <see cref="QuadPrefixTree"/> instances with useful defaults
-        /// </summary>
-        public class Factory : SpatialPrefixTreeFactory
-        {
-            protected internal override int GetLevelForDistance(double degrees)
-            {
-                var grid = new QuadPrefixTree(m_ctx, MAX_LEVELS_POSSIBLE);
-                return grid.GetLevelForDistance(degrees);
-            }
-
-            protected internal override SpatialPrefixTree NewSPT()
-            {
-                return new QuadPrefixTree(m_ctx, m_maxLevels ?? MAX_LEVELS_POSSIBLE);
-            }
-        }
-
-        #endregion
+        // LUCENENET specific - de-nested Factory and renamed QuadPrefixTreeFactory
 
         public const int MAX_LEVELS_POSSIBLE = 50;//not really sure how big this should be
 
@@ -74,9 +56,22 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         internal readonly int[] levelS; // side
         internal readonly int[] levelN; // number
 
+        /// <summary>
+        /// Initializes a new instance of <see cref="QuadPrefixTree"/> with the specified
+        /// spatial context (<paramref name="ctx"/>), <paramref name="bounds"/> and <paramref name="maxLevels"/>.
+        /// </summary>
+        /// <param name="ctx">The spatial context.</param>
+        /// <param name="bounds">The bounded rectangle.</param>
+        /// <param name="maxLevels">The maximum number of levels in the tree.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="ctx"/> or <paramref name="bounds"/> is <c>null</c>.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="maxLevels"/> is less than or equal to 0.</exception>
         public QuadPrefixTree(SpatialContext ctx, IRectangle bounds, int maxLevels)
             : base(ctx, maxLevels)
         {
+            // LUCENENET specific - added guard clause
+            if (bounds is null)
+                throw new ArgumentNullException(nameof(bounds));
+
             xmin = bounds.MinX;
             xmax = bounds.MaxX;
             ymin = bounds.MinY;
@@ -105,23 +100,41 @@ namespace Lucene.Net.Spatial.Prefix.Tree
             }
         }
 
+        /// <summary>
+        /// Initializes a new instance of <see cref="QuadPrefixTree"/> with the specified
+        /// spatial context (<paramref name="ctx"/>).
+        /// </summary>
+        /// <param name="ctx">The spatial context.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="ctx"/> is <c>null</c>.</exception>
         public QuadPrefixTree(SpatialContext ctx)
             : this(ctx, DEFAULT_MAX_LEVELS)
         {
         }
 
+        /// <summary>
+        /// Initializes a new instance of <see cref="QuadPrefixTree"/> with the specified
+        /// spatial context (<paramref name="ctx"/>) and <paramref name="maxLevels"/>.
+        /// </summary>
+        /// <param name="ctx">The spatial context.</param>
+        /// <param name="maxLevels">The maximum number of levels in the tree.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="ctx"/> is <c>null</c>.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="maxLevels"/> is less than or equal to 0.</exception>
         public QuadPrefixTree(SpatialContext ctx, int maxLevels)
-            : this(ctx, ctx.WorldBounds, maxLevels)
+            : this(ctx, ctx?.WorldBounds!, maxLevels)
         {
         }
 
-        public virtual void PrintInfo(TextWriter @out)
+        public virtual void PrintInfo(TextWriter output)
         {
+            // LUCENENET specific - added guard clause
+            if (output is null)
+                throw new ArgumentNullException(nameof(output));
+
             // Format the number to min 3 integer digits and exactly 5 fraction digits
             const string FORMAT_STR = @"000.00000";
             for (int i = 0; i < m_maxLevels; i++)
             {
-                @out.WriteLine(i + "]\t" + levelW[i].ToString(FORMAT_STR) + "\t" + levelH[i].ToString(FORMAT_STR) + "\t" +
+                output.WriteLine(i + "]\t" + levelW[i].ToString(FORMAT_STR) + "\t" + levelH[i].ToString(FORMAT_STR) + "\t" +
                                levelS[i] + "\t" + (levelS[i] * levelS[i]));
             }
         }
@@ -145,6 +158,10 @@ namespace Lucene.Net.Spatial.Prefix.Tree
 
         protected internal override Cell GetCell(IPoint p, int level)
         {
+            // LUCENENET specific - added guard clause
+            if (p is null)
+                throw new ArgumentNullException(nameof(p));
+
             IList<Cell> cells = new JCG.List<Cell>(1);
             Build(xmid, ymid, 0, cells, new StringBuilder(), m_ctx.MakePoint(p.X, p.Y), level);
             return cells[0];
@@ -156,9 +173,9 @@ namespace Lucene.Net.Spatial.Prefix.Tree
             return new QuadCell(this, token);
         }
 
-        public override Cell GetCell(byte[] bytes, int offset, int len)
+        public override Cell GetCell(byte[] bytes, int offset, int length)
         {
-            return new QuadCell(this, bytes, offset, len);
+            return new QuadCell(this, bytes, offset, length);
         }
 
         private void Build(
@@ -203,13 +220,13 @@ namespace Lucene.Net.Spatial.Prefix.Tree
             int strlen = str.Length;
             IRectangle rectangle = m_ctx.MakeRectangle(cx - w, cx + w, cy - h, cy + h);
             SpatialRelation v = shape.Relate(rectangle);
-            if (SpatialRelation.CONTAINS == v)
+            if (SpatialRelation.Contains == v)
             {
                 str.Append(c);
                 //str.append(SpatialPrefixGrid.COVER);
                 matches.Add(new QuadCell(this, str.ToString(), v.Transpose()));
             }
-            else if (SpatialRelation.DISJOINT == v)
+            else if (SpatialRelation.Disjoint == v)
             {
                 // nothing
             }
@@ -259,7 +276,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
 
             protected internal override ICollection<Cell> GetSubCells()
             {
-                QuadPrefixTree outerInstance = (QuadPrefixTree)this.m_outerInstance;
+                QuadPrefixTree outerInstance = (QuadPrefixTree)this.m_spatialPrefixTree;
                 return new JCG.List<Cell>(4)
                 {
                     new QuadCell(outerInstance, TokenString + "A"),
@@ -273,10 +290,10 @@ namespace Lucene.Net.Spatial.Prefix.Tree
 
             public override Cell GetSubCell(IPoint p)
             {
-                return m_outerInstance.GetCell(p, Level + 1);//not performant!
+                return m_spatialPrefixTree.GetCell(p, Level + 1);//not performant!
             }
 
-            private IShape shape; //cache
+            private IShape? shape; //cache
 
             public override IShape Shape
             {
@@ -292,7 +309,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
 
             private IRectangle MakeShape()
             {
-                QuadPrefixTree outerInstance = (QuadPrefixTree)this.m_outerInstance;
+                QuadPrefixTree outerInstance = (QuadPrefixTree)this.m_spatialPrefixTree;
                 string token = TokenString;
                 double xmin = outerInstance.xmin;
                 double ymin = outerInstance.ymin;
@@ -340,4 +357,29 @@ namespace Lucene.Net.Spatial.Prefix.Tree
 
         #endregion
     }
+
+    /// <summary>
+    /// Factory for creating <see cref="QuadPrefixTree"/> instances with useful defaults
+    /// </summary>
+    public class QuadPrefixTreeFactory : SpatialPrefixTreeFactory
+    {
+        protected internal override int GetLevelForDistance(double degrees)
+        {
+            // LUCENENET specific - added guard clause
+            if (m_ctx is null)
+                throw new InvalidOperationException($"{nameof(m_ctx)} must be set prior to calling GetLevelForDistance(double).");
+
+            var grid = new QuadPrefixTree(m_ctx, QuadPrefixTree.MAX_LEVELS_POSSIBLE);
+            return grid.GetLevelForDistance(degrees);
+        }
+
+        protected internal override SpatialPrefixTree NewSPT()
+        {
+            // LUCENENET specific - added guard clause
+            if (m_ctx is null)
+                throw new InvalidOperationException($"{nameof(m_ctx)} must be set prior to calling NewSPT().");
+
+            return new QuadPrefixTree(m_ctx, m_maxLevels ?? QuadPrefixTree.MAX_LEVELS_POSSIBLE);
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/Lucene.Net.Spatial/Prefix/Tree/SpatialPrefixTree.cs b/src/Lucene.Net.Spatial/Prefix/Tree/SpatialPrefixTree.cs
index af69659..b60118b 100644
--- a/src/Lucene.Net.Spatial/Prefix/Tree/SpatialPrefixTree.cs
+++ b/src/Lucene.Net.Spatial/Prefix/Tree/SpatialPrefixTree.cs
@@ -1,9 +1,9 @@
-using Lucene.Net.Diagnostics;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using J2N.Collections.Generic.Extensions;
+using Lucene.Net.Diagnostics;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
-using System.Collections.ObjectModel;
 using JCG = J2N.Collections.Generic;
 
 namespace Lucene.Net.Spatial.Prefix.Tree
@@ -34,10 +34,10 @@ namespace Lucene.Net.Spatial.Prefix.Tree
     /// at variable lengths corresponding to variable precision.   Each string
     /// corresponds to a rectangular spatial region.  This approach is
     /// also referred to "Grids", "Tiles", and "Spatial Tiers".
-    /// <p/>
+    /// <para/>
     /// Implementations of this class should be thread-safe and immutable once
     /// initialized.
-    /// 
+    /// <para/>
     /// @lucene.experimental
     /// </remarks>
     public abstract class SpatialPrefixTree
@@ -46,9 +46,22 @@ namespace Lucene.Net.Spatial.Prefix.Tree
 
         protected internal readonly SpatialContext m_ctx;
 
+        /// <summary>
+        /// Initializes a new instance of <see cref="SpatialPrefixTree"/> with the
+        /// specified spatial context and <paramref name="maxLevels"/>.
+        /// </summary>
+        /// <param name="ctx">The spatial context.</param>
+        /// <param name="maxLevels">The maximum number of levels in the tree.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="ctx"/> is <c>null</c>.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="maxLevels"/> is less than or equal to 0.</exception>
         protected SpatialPrefixTree(SpatialContext ctx, int maxLevels) // LUCENENET: CA1012: Abstract types should not have constructors (marked protected)
         {
-            if (Debugging.AssertsEnabled) Debugging.Assert(maxLevels > 0);
+            // LUCENENET specific - added guard clauses
+            if (ctx is null)
+                throw new ArgumentNullException(nameof(ctx));
+            if (maxLevels <= 0)
+                throw new ArgumentOutOfRangeException(nameof(maxLevels), $"{nameof(maxLevels)} must be greater than 0.");
+
             this.m_ctx = ctx;
             this.m_maxLevels = maxLevels;
         }
@@ -104,7 +117,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
             return Math.Sqrt(width * width + height * height);
         }
 
-        private Cell worldCell;//cached
+        private Cell? worldCell;//cached
 
         /// <summary>Returns the level 0 cell which encompasses all spatial data.</summary>
         /// <remarks>
@@ -136,7 +149,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
 
         public abstract Cell GetCell(byte[] bytes, int offset, int len);
 
-        public Cell GetCell(byte[] bytes, int offset, int len, Cell target)
+        public Cell GetCell(byte[] bytes, int offset, int len, Cell? target)
         {
             if (target is null)
             {
@@ -166,7 +179,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         /// This implementation checks if shape is a <see cref="IPoint"/> and if so returns
         /// <see cref="GetCells(IPoint, int, bool)"/>.
         /// </remarks>
-        /// <param name="shape">the shape; non-null</param>
+        /// <param name="shape">the shape</param>
         /// <param name="detailLevel">the maximum detail level to get cells for</param>
         /// <param name="inclParents">
         /// if true then all parent cells of leaves are returned
@@ -178,7 +191,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         /// ~20-25% fewer cells.
         /// </param>
         /// <returns>a set of cells (no dups), sorted, immutable, non-null</returns>
-        public virtual IList<Cell> GetCells(IShape shape, int detailLevel, bool inclParents, 
+        public virtual IList<Cell> GetCells(IShape? shape, int detailLevel, bool inclParents, 
             bool simplify)
         {
             //TODO consider an on-demand iterator -- it won't build up all cells in memory.
@@ -200,10 +213,15 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         /// Returns true if cell was added as a leaf. If it wasn't it recursively
         /// descends.
         /// </remarks>
-        private bool RecursiveGetCells(Cell cell, IShape shape, int detailLevel, 
+        /// <exception cref="ArgumentNullException"><paramref name="cell"/> is <c>null</c>.</exception>
+        private bool RecursiveGetCells(Cell cell, IShape? shape, int detailLevel, 
             bool inclParents, bool simplify, 
             IList<Cell> result)
         {
+            // LUCENENET specific - added guard clause
+            if (cell is null)
+                throw new ArgumentNullException(nameof(cell));
+
             if (cell.Level == detailLevel)
             {
                 cell.SetLeaf();//FYI might already be a leaf
@@ -258,12 +276,17 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         /// This implementation depends on <see cref="GetCell(string)"/> being fast, as its
         /// called repeatedly when incPlarents is true.
         /// </summary>
+        /// <exception cref="ArgumentNullException"><paramref name="p"/> is <c>null</c>.</exception>
         public virtual IList<Cell> GetCells(IPoint p, int detailLevel, bool inclParents)
         {
+            // LUCENENET specific - added guard clause
+            if (p is null)
+                throw new ArgumentNullException(nameof(p));
+
             Cell cell = GetCell(p, detailLevel);
             if (!inclParents)
             {
-                return new ReadOnlyCollection<Cell>(new[] { cell });
+                return new[] { cell }.AsReadOnly();
             }
             string endToken = cell.TokenString;
             if (Debugging.AssertsEnabled) Debugging.Assert(endToken.Length == detailLevel);
@@ -280,6 +303,9 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         [Obsolete("TODO remove; not used and not interesting, don't need collection in & out")]
         public static IList<string> CellsToTokenStrings(ICollection<Cell> cells)
         {
+            if (cells is null)
+                throw new ArgumentNullException(nameof(cells));
+
             IList<string> tokens = new JCG.List<string>((cells.Count));
             foreach (Cell cell in cells)
             {
diff --git a/src/Lucene.Net.Spatial/Prefix/Tree/SpatialPrefixTreeFactory.cs b/src/Lucene.Net.Spatial/Prefix/Tree/SpatialPrefixTreeFactory.cs
index da0ce34..05af159 100644
--- a/src/Lucene.Net.Spatial/Prefix/Tree/SpatialPrefixTreeFactory.cs
+++ b/src/Lucene.Net.Spatial/Prefix/Tree/SpatialPrefixTreeFactory.cs
@@ -1,8 +1,9 @@
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Distance;
+using Spatial4n.Context;
+using Spatial4n.Distance;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
+using System.Reflection;
 
 namespace Lucene.Net.Spatial.Prefix.Tree
 {
@@ -26,7 +27,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
     /// <summary>
     /// Abstract Factory for creating <see cref="SpatialPrefixTree"/> instances with useful
     /// defaults and passed on configurations defined in a <see cref="IDictionary{TKey, TValue}"/>.
-    /// 
+    /// <para/>
     /// @lucene.experimental
     /// </summary>
     public abstract class SpatialPrefixTreeFactory
@@ -36,8 +37,8 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         public const string MAX_LEVELS = "maxLevels";
         public const string MAX_DIST_ERR = "maxDistErr";
 
-        protected IDictionary<string, string> m_args;
-        protected SpatialContext m_ctx;
+        protected IDictionary<string, string>? m_args;
+        protected SpatialContext? m_ctx;
         protected int? m_maxLevels;
 
         /// <summary>The factory is looked up via "prefixTree" in <paramref name="args"/>, expecting "geohash" or "quad".</summary>
@@ -45,27 +46,47 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         /// The factory is looked up via "prefixTree" in <paramref name="args"/>, expecting "geohash" or "quad".
         /// If its neither of these, then "geohash" is chosen for a geo context, otherwise "quad" is chosen.
         /// </remarks>
-        public static SpatialPrefixTree MakeSPT(IDictionary<string, string> args, SpatialContext ctx)
+        /// <exception cref="ArgumentNullException"><paramref name="args"/> or <paramref name="ctx"/> is <c>null</c>.</exception>
+        public static SpatialPrefixTree MakeSPT(IDictionary<string, string> args, SpatialContext ctx) // LUCENENET specific overload for convenience.
+            => MakeSPT(args, null, ctx);
+
+        /// <summary>The factory is looked up via "prefixTree" in <paramref name="args"/>, expecting "geohash" or "quad".</summary>
+        /// <remarks>
+        /// The factory is looked up via "prefixTree" in <paramref name="args"/>, expecting "geohash" or "quad".
+        /// If its neither of these, then "geohash" is chosen for a geo context, otherwise "quad" is chosen.
+        /// </remarks>
+        /// <exception cref="ArgumentNullException"><paramref name="args"/> or <paramref name="ctx"/> is <c>null</c>.</exception>
+        public static SpatialPrefixTree MakeSPT(IDictionary<string, string> args, Assembly? assembly, SpatialContext ctx)
         {
-            SpatialPrefixTreeFactory instance;
-            if (!args.TryGetValue(PREFIX_TREE, out string cname))
+            // LUCENENET specific - added guard clauses
+            if (args is null)
+                throw new ArgumentNullException(nameof(args));
+            if (assembly is null)
+                assembly = typeof(SpatialPrefixTreeFactory).Assembly;
+            if (ctx is null)
+                throw new ArgumentNullException(nameof(ctx));
+
+            SpatialPrefixTreeFactory? instance;
+            if (!args.TryGetValue(PREFIX_TREE, out string? cname))
             {
                 cname = ctx.IsGeo ? "geohash" : "quad";
             }
             if ("geohash".Equals(cname, StringComparison.OrdinalIgnoreCase))
             {
-                instance = new GeohashPrefixTree.Factory();
+                instance = new GeohashPrefixTreeFactory();
             }
             else if ("quad".Equals(cname, StringComparison.OrdinalIgnoreCase))
             {
-                instance = new QuadPrefixTree.Factory();
+                instance = new QuadPrefixTreeFactory();
             }
             else
             {
                 try
                 {
-                    Type c = Type.GetType(cname);
-                    instance = (SpatialPrefixTreeFactory)Activator.CreateInstance(c);
+                    Type? c = assembly.GetType(cname) ?? Type.GetType(cname);
+                    if (c is null)
+                        throw RuntimeException.Create($"{cname} not found in {assembly.GetName().FullName} or by using Type.GetType(string).");// LUCENENET specific - .NET doesn't throw when the type is not found.
+                    instance = (SpatialPrefixTreeFactory)Activator.CreateInstance(c)!;
                 }
                 catch (Exception e) when (e.IsException())
                 {
@@ -78,27 +99,31 @@ namespace Lucene.Net.Spatial.Prefix.Tree
 
         protected internal virtual void Init(IDictionary<string, string> args, SpatialContext ctx)
         {
-            this.m_args = args;
-            this.m_ctx = ctx;
+            // LUCENENET specific - added guard clauses
+            this.m_args = args ?? throw new ArgumentNullException(nameof(args));
+            this.m_ctx = ctx ?? throw new ArgumentNullException(nameof(ctx));
             InitMaxLevels();
         }
 
         protected internal virtual void InitMaxLevels()
         {
-            if (m_args.TryGetValue(MAX_LEVELS, out string mlStr))
+            if (m_args is null || m_ctx is null)
+                throw new InvalidOperationException($"Init(IDictionary<string, string>, SpatialContext) must be called prior to calling InitMaxLevels()");
+
+            if (m_args.TryGetValue(MAX_LEVELS, out string? mlStr))
             {
                 m_maxLevels = int.Parse(mlStr, CultureInfo.InvariantCulture);
                 return;
             }
             double degrees;
-            if (!m_args.TryGetValue(MAX_DIST_ERR, out string maxDetailDistStr))
+            if (!m_args.TryGetValue(MAX_DIST_ERR, out string? maxDetailDistStr))
             {
                 if (!m_ctx.IsGeo)
                 {
                     return;
                 }
                 //let default to max
-                degrees = DistanceUtils.Dist2Degrees(DEFAULT_GEO_MAX_DETAIL_KM, DistanceUtils.EARTH_MEAN_RADIUS_KM);
+                degrees = DistanceUtils.Dist2Degrees(DEFAULT_GEO_MAX_DETAIL_KM, DistanceUtils.EarthMeanRadiusKilometers);
             }
             else
             {
@@ -108,7 +133,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         }
 
         /// <summary>
-        /// Calls <see cref="SpatialPrefixTree.GetLevelForDistance(double)">SpatialPrefixTree.GetLevelForDistance(double)</see>.
+        /// Calls <see cref="SpatialPrefixTree.GetLevelForDistance(double)" />.
         /// </summary>
         protected internal abstract int GetLevelForDistance(double degrees);
 
diff --git a/src/Lucene.Net.Spatial/Prefix/WithinPrefixTreeFilter.cs b/src/Lucene.Net.Spatial/Prefix/WithinPrefixTreeFilter.cs
index d61fc10..ca88222 100644
--- a/src/Lucene.Net.Spatial/Prefix/WithinPrefixTreeFilter.cs
+++ b/src/Lucene.Net.Spatial/Prefix/WithinPrefixTreeFilter.cs
@@ -3,12 +3,11 @@ using Lucene.Net.Index;
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Util;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Distance;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Distance;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.IO;
 
 namespace Lucene.Net.Spatial.Prefix
@@ -42,7 +41,7 @@ namespace Lucene.Net.Spatial.Prefix
     /// beyond the query shape's edge.  Even if the indexed shapes are sometimes
     /// comprised of multiple disjoint parts, you might want to use this option with
     /// a large buffer as a faster approximation with minimal false-positives.
-    /// 
+    /// <para/>
     /// @lucene.experimental
     /// </summary>
     public class WithinPrefixTreeFilter : AbstractVisitingPrefixTreeFilter
@@ -51,10 +50,10 @@ namespace Lucene.Net.Spatial.Prefix
         //  minimal query buffer by looking in a DocValues cache holding a representative
         //  point of each disjoint component of a document's shape(s).
 
-        private readonly IShape bufferedQueryShape;//if null then the whole world
+        private readonly IShape? bufferedQueryShape;//if null then the whole world
 
         /// <summary>
-        /// See <see cref="AbstractVisitingPrefixTreeFilter.AbstractVisitingPrefixTreeFilter(IShape, string, SpatialPrefixTree, int, int)"/>.
+        /// See <see cref="AbstractVisitingPrefixTreeFilter(IShape, string, SpatialPrefixTree, int, int)"/>.
         /// <c>queryBuffer</c> is the (minimum) distance beyond the query shape edge
         /// where non-matching documents are looked for so they can be excluded. If
         /// -1 is used then the whole world is examined (a good default for correctness).
@@ -78,6 +77,10 @@ namespace Lucene.Net.Spatial.Prefix
         /// </summary>
         protected virtual IShape BufferShape(IShape shape, double distErr)
         {
+            // LUCENENET specific - added guard clause
+            if (shape is null)
+                throw new ArgumentNullException(nameof(shape));
+
             //TODO move this generic code elsewhere?  Spatial4j?
             if (distErr <= 0)
             {
@@ -138,7 +141,7 @@ namespace Lucene.Net.Spatial.Prefix
         }
 
         /// <exception cref="IOException"></exception>
-        public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
+        public override DocIdSet? GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
         {
             return new VisitorTemplateAnonymousClass(this, context, acceptDocs, true).GetDocIdSet();
         }
@@ -147,73 +150,85 @@ namespace Lucene.Net.Spatial.Prefix
 
         private sealed class VisitorTemplateAnonymousClass : VisitorTemplate
         {
-            private FixedBitSet inside;
-            private FixedBitSet outside;
+            private FixedBitSet? inside;
+            private FixedBitSet? outside;
             private SpatialRelation visitRelation;
 
             public VisitorTemplateAnonymousClass(WithinPrefixTreeFilter outerInstance, AtomicReaderContext context, 
-                IBits acceptDocs, bool hasIndexedLeaves)
+                IBits? acceptDocs, bool hasIndexedLeaves)
                 : base(outerInstance, context, acceptDocs, hasIndexedLeaves)
             {
             }
 
-            protected internal override void Start()
+            protected override void Start()
             {
                 inside = new FixedBitSet(m_maxDoc);
                 outside = new FixedBitSet(m_maxDoc);
             }
 
-            protected internal override DocIdSet Finish()
+            protected override DocIdSet Finish()
             {
-                inside.AndNot(outside);
+                inside!.AndNot(outside!);
                 return inside;
             }
 
-            protected internal override IEnumerator<Cell> FindSubCellsToVisit(Cell cell)
+            protected override IEnumerator<Cell> FindSubCellsToVisit(Cell cell)
             {
+                // LUCENENET specific - added guard clause
+                if (cell is null)
+                    throw new ArgumentNullException(nameof(cell));
+
                 //use buffered query shape instead of orig.  Works with null too.
-                return cell.GetSubCells(((WithinPrefixTreeFilter)m_outerInstance).bufferedQueryShape).GetEnumerator();
+                return cell.GetSubCells(((WithinPrefixTreeFilter)m_filter).bufferedQueryShape).GetEnumerator();
             }
 
-            protected internal override bool Visit(Cell cell)
+            protected override bool Visit(Cell cell)
             {
+                // LUCENENET specific - added guard clause
+                if (cell is null)
+                    throw new ArgumentNullException(nameof(cell));
+
                 //cell.relate is based on the bufferedQueryShape; we need to examine what
                 // the relation is against the queryShape
-                visitRelation = cell.Shape.Relate(m_outerInstance.m_queryShape);
-                if (visitRelation == SpatialRelation.WITHIN)
+                visitRelation = cell.Shape.Relate(m_filter.m_queryShape);
+                if (visitRelation == SpatialRelation.Within)
                 {
-                    CollectDocs(inside);
+                    CollectDocs(inside!);
                     return false;
                 }
-                else if (visitRelation == SpatialRelation.DISJOINT)
+                else if (visitRelation == SpatialRelation.Disjoint)
                 {
-                    CollectDocs(outside);
+                    CollectDocs(outside!);
                     return false;
                 }
-                else if (cell.Level == m_outerInstance.m_detailLevel)
+                else if (cell.Level == m_filter.m_detailLevel)
                 {
-                    CollectDocs(inside);
+                    CollectDocs(inside!);
                     return false;
                 }
                 return true;
             }
 
             /// <exception cref="IOException"></exception>
-            protected internal override void VisitLeaf(Cell cell)
+            protected override void VisitLeaf(Cell cell)
             {
+                // LUCENENET specific - added guard clause
+                if (cell is null)
+                    throw new ArgumentNullException(nameof(cell));
+
                 //visitRelation is declared as a field, populated by visit() so we don't recompute it
                 if (Debugging.AssertsEnabled)
                 {
-                    Debugging.Assert(m_outerInstance.m_detailLevel != cell.Level);
-                    Debugging.Assert(visitRelation == cell.Shape.Relate(m_outerInstance.m_queryShape));
+                    Debugging.Assert(m_filter.m_detailLevel != cell.Level);
+                    Debugging.Assert(visitRelation == cell.Shape.Relate(m_filter.m_queryShape));
                 }
                 if (AllCellsIntersectQuery(cell, visitRelation))
                 {
-                    CollectDocs(inside);
+                    CollectDocs(inside!);
                 }
                 else
                 {
-                    CollectDocs(outside);
+                    CollectDocs(outside!);
                 }
             }
 
@@ -223,19 +238,23 @@ namespace Lucene.Net.Spatial.Prefix
             /// </summary>
             private bool AllCellsIntersectQuery(Cell cell, SpatialRelation relate/*cell to query*/)
             {
-                if (relate == SpatialRelation.NOT_SET)
+                // LUCENENET specific - added guard clause
+                if (cell is null)
+                    throw new ArgumentNullException(nameof(cell));
+
+                if (relate == SpatialRelation.None)
                 {
-                    relate = cell.Shape.Relate(m_outerInstance.m_queryShape);
+                    relate = cell.Shape.Relate(m_filter.m_queryShape);
                 }
-                if (cell.Level == m_outerInstance.m_detailLevel)
+                if (cell.Level == m_filter.m_detailLevel)
                 {
                     return relate.Intersects();
                 }
-                if (relate == SpatialRelation.WITHIN)
+                if (relate == SpatialRelation.Within)
                 {
                     return true;
                 }
-                if (relate == SpatialRelation.DISJOINT)
+                if (relate == SpatialRelation.Disjoint)
                 {
                     return false;
                 }
@@ -245,7 +264,7 @@ namespace Lucene.Net.Spatial.Prefix
                 ICollection<Cell> subCells = cell.GetSubCells(null);
                 foreach (Cell subCell in subCells)
                 {
-                    if (!AllCellsIntersectQuery(subCell, SpatialRelation.NOT_SET))
+                    if (!AllCellsIntersectQuery(subCell, SpatialRelation.None))
                     {
                         //recursion
                         return false;
@@ -255,15 +274,19 @@ namespace Lucene.Net.Spatial.Prefix
             }
 
             /// <exception cref="IOException"></exception>
-            protected internal override void VisitScanned(Cell cell)
+            protected override void VisitScanned(Cell cell)
             {
-                if (AllCellsIntersectQuery(cell, SpatialRelation.NOT_SET))
+                // LUCENENET specific - added guard clause
+                if (cell is null)
+                    throw new ArgumentNullException(nameof(cell));
+
+                if (AllCellsIntersectQuery(cell, SpatialRelation.None))
                 {
-                    CollectDocs(inside);
+                    CollectDocs(inside!);
                 }
                 else
                 {
-                    CollectDocs(outside);
+                    CollectDocs(outside!);
                 }
             }
         }
diff --git a/src/Lucene.Net.Spatial/Query/SpatialArgs.cs b/src/Lucene.Net.Spatial/Query/SpatialArgs.cs
index 2927c9f..2a2c05c 100644
--- a/src/Lucene.Net.Spatial/Query/SpatialArgs.cs
+++ b/src/Lucene.Net.Spatial/Query/SpatialArgs.cs
@@ -1,5 +1,5 @@
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 
 namespace Lucene.Net.Spatial.Queries
@@ -39,8 +39,8 @@ namespace Lucene.Net.Spatial.Queries
         public SpatialArgs(SpatialOperation operation, IShape shape)
         {
             // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
-            this.Operation = operation ?? throw new ArgumentNullException(nameof(operation), "operation and shape are required");
-            this.Shape = shape ?? throw new ArgumentNullException(nameof(shape), "operation and shape are required");
+            this.operation = operation ?? throw new ArgumentNullException(nameof(operation), "operation and shape are required");
+            this.shape = shape ?? throw new ArgumentNullException(nameof(shape), "operation and shape are required");
         }
 
         /// <summary>
@@ -54,7 +54,9 @@ namespace Lucene.Net.Spatial.Queries
         /// <returns>A distance (in degrees).</returns>
         public static double CalcDistanceFromErrPct(IShape shape, double distErrPct, SpatialContext ctx)
         {
-            // LUCENENET: Added null guard clause
+            // LUCENENET: Added null guard clauses
+            if (shape is null)
+                throw new ArgumentNullException(nameof(shape));
             if (ctx is null)
                 throw new ArgumentNullException(nameof(ctx));
 
@@ -73,7 +75,7 @@ namespace Lucene.Net.Spatial.Queries
             // take the closest one (greater precision).
             IPoint ctr = bbox.Center;
             double y = (ctr.Y >= 0 ? bbox.MaxY : bbox.MinY);
-            double diagonalDist = ctx.DistCalc.Distance(ctr, bbox.MaxX, y);
+            double diagonalDist = ctx.DistanceCalculator.Distance(ctr, bbox.MaxX, y);
             return diagonalDist * distErrPct;
         }
 
@@ -89,6 +91,9 @@ namespace Lucene.Net.Spatial.Queries
         {
             if (DistErr != null)
                 return DistErr.Value;
+            // LUCENENET specific - added guard clause
+            if (ctx is null)
+                throw new ArgumentNullException(nameof(ctx));
             double distErrPct = (this.distErrPct ?? defaultDistErrPct);
             return CalcDistanceFromErrPct(Shape, distErrPct, ctx);
         }
@@ -121,13 +126,13 @@ namespace Lucene.Net.Spatial.Queries
         public virtual SpatialOperation Operation
         {
             get => operation;
-            set => operation = value;
+            set => operation = value ?? throw new ArgumentNullException(nameof(Operation)); // LUCENENET specific - added guard clause
         }
 
         public virtual IShape Shape
         {
             get => shape;
-            set => shape = value;
+            set => shape = value ?? throw new ArgumentNullException(nameof(Shape)); // LUCENENET specific - added guard clause
         }
 
         /// <summary>
diff --git a/src/Lucene.Net.Spatial/Query/SpatialArgsParser.cs b/src/Lucene.Net.Spatial/Query/SpatialArgsParser.cs
index 5ea9ce5..42e16c7 100644
--- a/src/Lucene.Net.Spatial/Query/SpatialArgsParser.cs
+++ b/src/Lucene.Net.Spatial/Query/SpatialArgsParser.cs
@@ -1,7 +1,7 @@
 using J2N.Text;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Exceptions;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Exceptions;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
@@ -29,7 +29,7 @@ namespace Lucene.Net.Spatial.Queries
     /// <summary>
     /// Parses a string that usually looks like "OPERATION(SHAPE)" into a <see cref="SpatialArgs"/>
     /// object. The set of operations supported are defined in <see cref="SpatialOperation"/>, such
-    /// as "Intersects" being a common one. The shape portion is defined by WKT <see cref="Spatial4n.Core.IO.WktShapeParser"/>,
+    /// as "Intersects" being a common one. The shape portion is defined by WKT <see cref="Spatial4n.IO.WktShapeParser"/>,
     /// but it can be overridden/customized via <see cref="ParseShape(string, SpatialContext)"/>.
     /// There are some optional name-value pair parameters that follow the closing parenthesis.  Example:
     /// <code>
@@ -39,7 +39,7 @@ namespace Lucene.Net.Spatial.Queries
     /// In the future it would be good to support something at least semi-standardized like a
     /// variant of <a href="http://docs.geoserver.org/latest/en/user/filter/ecql_reference.html#spatial-predicate">
     ///   [E]CQL</a>.
-    ///   
+    /// <para/>
     /// @lucene.experimental
     /// </summary>
     public class SpatialArgsParser
@@ -52,6 +52,10 @@ namespace Lucene.Net.Spatial.Queries
         /// </summary>
         public static string WriteSpatialArgs(SpatialArgs args)
         {
+            // LUCENENET specific - added guard clause
+            if (args is null)
+                throw new ArgumentNullException(nameof(args));
+
             var str = new StringBuilder();
             str.Append(args.Operation.Name);
             str.Append('(');
@@ -70,17 +74,24 @@ namespace Lucene.Net.Spatial.Queries
         /// <param name="v">The string to parse. Mandatory.</param>
         /// <param name="ctx">The spatial context. Mandatory.</param>
         /// <returns>Not null.</returns>
-        /// <exception cref="ArgumentException">if the parameters don't make sense or an add-on parameter is unknown</exception>
-        /// <exception cref="Spatial4n.Core.Exceptions.ParseException">If there is a problem parsing the string</exception>
-        /// <exception cref="InvalidShapeException">When the coordinates are invalid for the shape</exception>
+        /// <exception cref="ArgumentException">if the parameters don't make sense or an add-on parameter is unknown.</exception>
+        /// <exception cref="Spatial4n.Exceptions.ParseException">If there is a problem parsing the string.</exception>
+        /// <exception cref="InvalidShapeException">When the coordinates are invalid for the shape.</exception>
+        /// <exception cref="ArgumentNullException"><paramref name="v"/> or <paramref name="ctx"/> is <c>null</c>.</exception>
         public virtual SpatialArgs Parse(string v, SpatialContext ctx)
         {
+            // LUCENENET specific - added guard clauses
+            if (v is null)
+                throw new ArgumentNullException(nameof(v));
+            if (ctx is null)
+                throw new ArgumentNullException(nameof(ctx));
+
             int idx = v.IndexOf('(');
             int edx = v.LastIndexOf(')');
 
             if (idx < 0 || idx > edx)
             {
-                throw new Spatial4n.Core.Exceptions.ParseException("missing parens: " + v, -1);
+                throw new Spatial4n.Exceptions.ParseException("missing parens: " + v, -1);
             }
 
             SpatialOperation op = SpatialOperation.Get(v.Substring(0, idx - 0).Trim());
@@ -90,7 +101,7 @@ namespace Lucene.Net.Spatial.Queries
             string body = v.Substring(idx + 1, edx - (idx + 1)).Trim();
             if (body.Length < 1)
             {
-                throw new Spatial4n.Core.Exceptions.ParseException("missing body : " + v, idx + 1);
+                throw new Spatial4n.Exceptions.ParseException("missing body : " + v, idx + 1);
             }
 
             var shape = ParseShape(body, ctx);
@@ -118,28 +129,42 @@ namespace Lucene.Net.Spatial.Queries
             return new SpatialArgs(op, shape);
         }
 
+        /// <exception cref="ArgumentNullException"><paramref name="args"/> or <paramref name="nameValPairs"/> is <c>null</c>.</exception>
         protected virtual void ReadNameValuePairs(SpatialArgs args, IDictionary<string, string> nameValPairs)
         {
-            nameValPairs.TryGetValue(DIST_ERR_PCT, out string distErrPctStr);
-            nameValPairs.TryGetValue(DIST_ERR, out string distErrStr);
+            // LUCENENET specific - added guard clause
+            if (args is null)
+                throw new ArgumentNullException(nameof(args));
+            if (nameValPairs is null)
+                throw new ArgumentNullException(nameof(nameValPairs));
+
+            nameValPairs.TryGetValue(DIST_ERR_PCT, out string? distErrPctStr);
+            nameValPairs.TryGetValue(DIST_ERR, out string? distErrStr);
             args.DistErrPct = ReadDouble(distErrPctStr);
             nameValPairs.Remove(DIST_ERR_PCT);
             args.DistErr = ReadDouble(distErrStr);
             nameValPairs.Remove(DIST_ERR);
         }
 
+        /// <exception cref="ArgumentNullException"><paramref name="str"/> or <paramref name="ctx"/> is <c>null</c>.</exception>
         protected virtual IShape ParseShape(string str, SpatialContext ctx) 
         {
+            // LUCENENET specific - added guard clauses
+            if (str is null)
+                throw new ArgumentNullException(nameof(str));
+            if (ctx is null)
+                throw new ArgumentNullException(nameof(ctx));
+            
             //return ctx.readShape(str);//still in Spatial4n 0.4 but will be deleted
             return ctx.ReadShapeFromWkt(str);
         }
 
-        protected static double? ReadDouble(string v)
+        protected static double? ReadDouble(string? v)
         {
             return double.TryParse(v, NumberStyles.Float, CultureInfo.InvariantCulture, out double val) ? val : (double?)null;
         }
 
-        protected static bool ReadBool(string v, bool defaultValue)
+        protected static bool ReadBool(string? v, bool defaultValue)
         {
             return bool.TryParse(v, out bool ret) ? ret : defaultValue;
         }
@@ -148,8 +173,13 @@ namespace Lucene.Net.Spatial.Queries
         /// Parses "a=b c=d f" (whitespace separated) into name-value pairs. If there
         /// is no '=' as in 'f' above then it's short for f=f.
         /// </summary>
+        /// <exception cref="ArgumentNullException"><paramref name="body"/> is <c>null</c>.</exception>
         protected static IDictionary<string, string> ParseMap(string body)
         {
+            // LUCENENET specific - added guard clause
+            if (body is null)
+                throw new ArgumentNullException(nameof(body));
+
             var map = new Dictionary<string, string>();
             StringTokenizer st = new StringTokenizer(body, " \n\t");
 
diff --git a/src/Lucene.Net.Spatial/Query/SpatialOperation.cs b/src/Lucene.Net.Spatial/Query/SpatialOperation.cs
index c2bcf84..8072e76 100644
--- a/src/Lucene.Net.Spatial/Query/SpatialOperation.cs
+++ b/src/Lucene.Net.Spatial/Query/SpatialOperation.cs
@@ -1,4 +1,4 @@
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
@@ -54,6 +54,12 @@ namespace Lucene.Net.Spatial.Queries
 
             public override bool Evaluate(IShape indexedShape, IShape queryShape)
             {
+                // LUCENENET specific - added guard clauses
+                if (indexedShape is null)
+                    throw new ArgumentNullException(nameof(indexedShape));
+                if (queryShape is null)
+                    throw new ArgumentNullException(nameof(queryShape));
+
                 return indexedShape.BoundingBox.Relate(queryShape).Intersects();
             }
         }
@@ -71,8 +77,14 @@ namespace Lucene.Net.Spatial.Queries
 
             public override bool Evaluate(IShape indexedShape, IShape queryShape)
             {
+                // LUCENENET specific - added guard clauses
+                if (indexedShape is null)
+                    throw new ArgumentNullException(nameof(indexedShape));
+                if (queryShape is null)
+                    throw new ArgumentNullException(nameof(queryShape));
+
                 IRectangle bbox = indexedShape.BoundingBox;
-                return bbox.Relate(queryShape) == SpatialRelation.WITHIN || bbox.Equals(queryShape);
+                return bbox.Relate(queryShape) == SpatialRelation.Within || bbox.Equals(queryShape);
             }
         }
 
@@ -86,7 +98,13 @@ namespace Lucene.Net.Spatial.Queries
 
             public override bool Evaluate(IShape indexedShape, IShape queryShape)
             {
-                return indexedShape.HasArea && indexedShape.Relate(queryShape) == SpatialRelation.CONTAINS || indexedShape.Equals(queryShape);
+                // LUCENENET specific - added guard clauses
+                if (indexedShape is null)
+                    throw new ArgumentNullException(nameof(indexedShape));
+                if (queryShape is null)
+                    throw new ArgumentNullException(nameof(queryShape));
+
+                return indexedShape.HasArea && indexedShape.Relate(queryShape) == SpatialRelation.Contains || indexedShape.Equals(queryShape);
             }
         }
 
@@ -100,6 +118,12 @@ namespace Lucene.Net.Spatial.Queries
 
             public override bool Evaluate(IShape indexedShape, IShape queryShape)
             {
+                // LUCENENET specific - added guard clauses
+                if (indexedShape is null)
+                    throw new ArgumentNullException(nameof(indexedShape));
+                if (queryShape is null)
+                    throw new ArgumentNullException(nameof(queryShape));
+
                 return indexedShape.Relate(queryShape).Intersects();
             }
         }
@@ -114,6 +138,12 @@ namespace Lucene.Net.Spatial.Queries
 
             public override bool Evaluate(IShape indexedShape, IShape queryShape)
             {
+                // LUCENENET specific - added guard clauses
+                if (indexedShape is null)
+                    throw new ArgumentNullException(nameof(indexedShape));
+                if (queryShape is null)
+                    throw new ArgumentNullException(nameof(queryShape));
+
                 return indexedShape.Equals(queryShape);
             }
         }
@@ -128,6 +158,12 @@ namespace Lucene.Net.Spatial.Queries
 
             public override bool Evaluate(IShape indexedShape, IShape queryShape)
             {
+                // LUCENENET specific - added guard clauses
+                if (indexedShape is null)
+                    throw new ArgumentNullException(nameof(indexedShape));
+                if (queryShape is null)
+                    throw new ArgumentNullException(nameof(queryShape));
+
                 return !indexedShape.Relate(queryShape).Intersects();
             }
         }
@@ -142,7 +178,13 @@ namespace Lucene.Net.Spatial.Queries
 
             public override bool Evaluate(IShape indexedShape, IShape queryShape)
             {
-                return queryShape.HasArea && (indexedShape.Relate(queryShape) == SpatialRelation.WITHIN || indexedShape.Equals(queryShape));
+                // LUCENENET specific - added guard clauses
+                if (indexedShape is null)
+                    throw new ArgumentNullException(nameof(indexedShape));
+                if (queryShape is null)
+                    throw new ArgumentNullException(nameof(queryShape));
+
+                return queryShape.HasArea && (indexedShape.Relate(queryShape) == SpatialRelation.Within || indexedShape.Equals(queryShape));
             }
         }
 
@@ -156,6 +198,12 @@ namespace Lucene.Net.Spatial.Queries
 
             public override bool Evaluate(IShape indexedShape, IShape queryShape)
             {
+                // LUCENENET specific - added guard clauses
+                if (indexedShape is null)
+                    throw new ArgumentNullException(nameof(indexedShape));
+                if (queryShape is null)
+                    throw new ArgumentNullException(nameof(queryShape));
+
                 return queryShape.HasArea && indexedShape.Relate(queryShape).Intersects();
             }
         }
@@ -180,7 +228,7 @@ namespace Lucene.Net.Spatial.Queries
 
         public static SpatialOperation Get(string v)
         {
-            if (!registry.TryGetValue(v, out SpatialOperation op) || op is null)
+            if (!registry.TryGetValue(v, out SpatialOperation? op) || op is null)
             {
                 if (!registry.TryGetValue(CultureInfo.InvariantCulture.TextInfo.ToUpper(v), out op) || op is null)
                     throw new ArgumentException($"Unknown Operation: {v}", nameof(v));
@@ -204,6 +252,7 @@ namespace Lucene.Net.Spatial.Queries
         /// Returns whether the relationship between <paramref name="indexedShape"/> and <paramref name="queryShape"/> is
         /// satisfied by this operation.
         /// </summary>
+        /// <exception cref="ArgumentNullException"><paramref name="indexedShape"/> or <paramref name="queryShape"/> is <c>null</c>.</exception>
         public abstract bool Evaluate(IShape indexedShape, IShape queryShape);
 
         // ================================================= Getters / Setters =============================================
diff --git a/src/Lucene.Net.Spatial/Query/UnsupportedSpatialOperation.cs b/src/Lucene.Net.Spatial/Query/UnsupportedSpatialOperation.cs
index d3952b1..9e1c57d 100644
--- a/src/Lucene.Net.Spatial/Query/UnsupportedSpatialOperation.cs
+++ b/src/Lucene.Net.Spatial/Query/UnsupportedSpatialOperation.cs
@@ -24,7 +24,7 @@ namespace Lucene.Net.Spatial.Queries
 
     /// <summary>
     /// Exception thrown when the <see cref="SpatialStrategy"/> cannot implement the requested operation.
-    /// 
+    /// <para/>
     /// @lucene.experimental
     /// </summary>
     // LUCENENET: It is no longer good practice to use binary serialization. 
@@ -32,15 +32,15 @@ namespace Lucene.Net.Spatial.Queries
 #if FEATURE_SERIALIZABLE_EXCEPTIONS
     [Serializable]
 #endif
-    public class UnsupportedSpatialOperation : NotSupportedException
+    public class UnsupportedSpatialOperationException : NotSupportedException
     {
-        public UnsupportedSpatialOperation(SpatialOperation op)
-            : base(op.Name)
+        public UnsupportedSpatialOperationException(SpatialOperation? op)
+            : base(op?.Name)
         {
         }
 
         // For testing
-        internal UnsupportedSpatialOperation(string message)
+        internal UnsupportedSpatialOperationException(string? message)
             : base(message)
         {
         }
@@ -51,7 +51,7 @@ namespace Lucene.Net.Spatial.Queries
         /// </summary>
         /// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
         /// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param>
-        protected UnsupportedSpatialOperation(SerializationInfo info, StreamingContext context)
+        protected UnsupportedSpatialOperationException(SerializationInfo info, StreamingContext context)
             : base(info, context)
         {
         }
diff --git a/src/Lucene.Net.Spatial/Serialized/SerializedDVStrategy.cs b/src/Lucene.Net.Spatial/Serialized/SerializedDVStrategy.cs
index 551ce9a..df3960a 100644
--- a/src/Lucene.Net.Spatial/Serialized/SerializedDVStrategy.cs
+++ b/src/Lucene.Net.Spatial/Serialized/SerializedDVStrategy.cs
@@ -5,9 +5,9 @@ using Lucene.Net.Search;
 using Lucene.Net.Spatial.Queries;
 using Lucene.Net.Spatial.Util;
 using Lucene.Net.Util;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.IO;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.IO;
+using Spatial4n.Shapes;
 using System;
 using System.Collections;
 using System.IO;
@@ -52,6 +52,7 @@ namespace Lucene.Net.Spatial.Serialized
         /// <summary>
         /// Constructs the spatial strategy with its mandatory arguments.
         /// </summary>
+        /// <exception cref="ArgumentNullException"><paramref name="ctx"/> or <paramref name="fieldName"/> is <c>null</c> or <paramref name="fieldName"/> is empty.</exception>
         public SerializedDVStrategy(SpatialContext ctx, string fieldName)
             : base(ctx, fieldName)
         {
@@ -59,6 +60,10 @@ namespace Lucene.Net.Spatial.Serialized
 
         public override Field[] CreateIndexableFields(IShape shape)
         {
+            // LUCENENET specific - added guard clause
+            if (shape is null)
+                throw new ArgumentNullException(nameof(shape));
+
             int bufSize = Math.Max(128, (int)(this.indexLastBufSize * 1.5));//50% headroom over last
             MemoryStream byteStream = new MemoryStream(bufSize);
             BytesRef bytesRef = new BytesRef();//receiver of byteStream's bytes
@@ -83,7 +88,8 @@ namespace Lucene.Net.Spatial.Serialized
 
             public OutputStreamAnonymousClass(BytesRef bytesRef)
             {
-                this.bytesRef = bytesRef;
+                // LUCENENET specific - added guard clause
+                this.bytesRef = bytesRef ?? throw new ArgumentNullException(nameof(bytesRef));
             }
 
             public override void Write(byte[] buffer, int index, int count)
@@ -113,6 +119,10 @@ namespace Lucene.Net.Spatial.Serialized
         /// </summary>
         public override Filter MakeFilter(SpatialArgs args)
         {
+            // LUCENENET specific - added guard clause
+            if (args is null)
+                throw new ArgumentNullException(nameof(args));
+
             ValueSource shapeValueSource = MakeShapeValueSource();
             ShapePredicateValueSource predicateValueSource = new ShapePredicateValueSource(
                 shapeValueSource, args.Operation, args.Shape);
@@ -139,10 +149,11 @@ namespace Lucene.Net.Spatial.Serialized
 
             public PredicateValueSourceFilter(ValueSource predicateValueSource)
             {
-                this.predicateValueSource = predicateValueSource;
+                // LUCENENET specific - added guard clause
+                this.predicateValueSource = predicateValueSource ?? throw new ArgumentNullException(nameof(predicateValueSource));
             }
 
-            public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
+            public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits? acceptDocs)
             {
                 return new DocIdSetAnonymousClass(this, context, acceptDocs);
             }
@@ -151,12 +162,13 @@ namespace Lucene.Net.Spatial.Serialized
             {
                 private readonly PredicateValueSourceFilter outerInstance;
                 private readonly AtomicReaderContext context;
-                private readonly IBits acceptDocs;
+                private readonly IBits? acceptDocs;
 
-                public DocIdSetAnonymousClass(PredicateValueSourceFilter outerInstance, AtomicReaderContext context, IBits acceptDocs)
+                public DocIdSetAnonymousClass(PredicateValueSourceFilter outerInstance, AtomicReaderContext context, IBits? acceptDocs)
                 {
-                    this.outerInstance = outerInstance;
-                    this.context = context;
+                    // LUCENENET specific - added guard clauses
+                    this.outerInstance = outerInstance ?? throw new ArgumentNullException(nameof(outerInstance));
+                    this.context = context ?? throw new ArgumentNullException(nameof(context));
                     this.acceptDocs = acceptDocs;
                 }
 
@@ -182,12 +194,13 @@ namespace Lucene.Net.Spatial.Serialized
                 {
                     private readonly FunctionValues predFuncValues;
                     private readonly AtomicReaderContext context;
-                    private readonly IBits acceptDocs;
+                    private readonly IBits? acceptDocs;
 
-                    public BitsAnonymousClass(FunctionValues predFuncValues, AtomicReaderContext context, IBits acceptDocs)
+                    public BitsAnonymousClass(FunctionValues predFuncValues, AtomicReaderContext context, IBits? acceptDocs)
                     {
-                        this.predFuncValues = predFuncValues;
-                        this.context = context;
+                        // LUCENENET specific - added guard clauses
+                        this.predFuncValues = predFuncValues ?? throw new ArgumentNullException(nameof(predFuncValues));
+                        this.context = context ?? throw new ArgumentNullException(nameof(context));
                         this.acceptDocs = acceptDocs;
                     }
 
@@ -202,7 +215,7 @@ namespace Lucene.Net.Spatial.Serialized
                 }
             }
 
-            public override bool Equals(object o)
+            public override bool Equals(object? o)
             {
                 if (this == o) return true;
                 if (o is null || GetType() != o.GetType()) return false;
@@ -232,12 +245,17 @@ namespace Lucene.Net.Spatial.Serialized
 
             internal ShapeDocValueSource(string fieldName, BinaryCodec binaryCodec)
             {
-                this.fieldName = fieldName;
-                this.binaryCodec = binaryCodec;
+                // LUCENENET specific - added guard clauses
+                this.fieldName = fieldName ?? throw new ArgumentNullException(nameof(fieldName));
+                this.binaryCodec = binaryCodec ?? throw new ArgumentNullException(nameof(binaryCodec));
             }
 
             public override FunctionValues GetValues(IDictionary context, AtomicReaderContext readerContext)
             {
+                // LUCENENET specific - added guard clause
+                if (readerContext is null)
+                    throw new ArgumentNullException(nameof(readerContext));
+
                 BinaryDocValues docValues = readerContext.AtomicReader.GetBinaryDocValues(fieldName);
 
                 return new FuctionValuesAnonymousClass(this, docValues);
@@ -250,8 +268,9 @@ namespace Lucene.Net.Spatial.Serialized
 
                 public FuctionValuesAnonymousClass(ShapeDocValueSource outerInstance, BinaryDocValues docValues)
                 {
-                    this.outerInstance = outerInstance;
-                    this.docValues = docValues;
+                    // LUCENENET specific - added guard clauses
+                    this.outerInstance = outerInstance ?? throw new ArgumentNullException(nameof(outerInstance));
+                    this.docValues = docValues ?? throw new ArgumentNullException(nameof(docValues));
                 }
 
                 private int bytesRefDoc = -1;
@@ -274,6 +293,10 @@ namespace Lucene.Net.Spatial.Serialized
 
                 public override bool BytesVal(int doc, BytesRef target)
                 {
+                    // LUCENENET specific - added guard clause
+                    if (target is null)
+                        throw new ArgumentNullException(nameof(target));
+
                     if (FillBytes(doc))
                     {
                         target.Bytes = bytesRef.Bytes;
@@ -288,7 +311,7 @@ namespace Lucene.Net.Spatial.Serialized
                     }
                 }
 
-                public override object ObjectVal(int docId)
+                public override object? ObjectVal(int docId)
                 {
                     if (!FillBytes(docId))
                         return null;
@@ -315,7 +338,7 @@ namespace Lucene.Net.Spatial.Serialized
                 }
             }
 
-            public override bool Equals(object o)
+            public override bool Equals(object? o)
             {
                 if (this == o) return true;
                 if (o is null || GetType() != o.GetType()) return false;
diff --git a/src/Lucene.Net.Spatial/SpatialStrategy.cs b/src/Lucene.Net.Spatial/SpatialStrategy.cs
index f0f6e38..fe3783c 100644
--- a/src/Lucene.Net.Spatial/SpatialStrategy.cs
+++ b/src/Lucene.Net.Spatial/SpatialStrategy.cs
@@ -3,8 +3,8 @@ using Lucene.Net.Queries.Function;
 using Lucene.Net.Queries.Function.ValueSources;
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Queries;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 
 namespace Lucene.Net.Spatial
@@ -46,7 +46,7 @@ namespace Lucene.Net.Spatial
     /// immaterial to indexing and search.
     /// <para/>
     /// Thread-safe.
-    /// 
+    /// <para/>
     /// @lucene.experimental
     /// </summary>
     public abstract class SpatialStrategy
@@ -57,6 +57,7 @@ namespace Lucene.Net.Spatial
         /// <summary>
         /// Constructs the spatial strategy with its mandatory arguments.
         /// </summary>
+        /// <exception cref="ArgumentNullException"><paramref name="ctx"/> or <paramref name="fieldName"/> is <c>null</c> or <paramref name="fieldName"/> is empty.</exception>
         protected SpatialStrategy(SpatialContext ctx, string fieldName)
         {
             this.m_ctx = ctx ?? throw new ArgumentNullException(nameof(ctx), "ctx is required");// LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
@@ -116,7 +117,7 @@ namespace Lucene.Net.Spatial
         /// <code>return new ConstantScoreQuery(MakeFilter(args));</code>
         /// </summary>
         /// <exception cref="NotSupportedException">If the strategy does not support the shape in <paramref name="args"/>.</exception>
-        /// <exception cref="UnsupportedSpatialOperation">If the strategy does not support the <see cref="SpatialOperation"/> in <paramref name="args"/>.</exception>
+        /// <exception cref="UnsupportedSpatialOperationException">If the strategy does not support the <see cref="SpatialOperation"/> in <paramref name="args"/>.</exception>
         public virtual ConstantScoreQuery MakeQuery(SpatialArgs args)
         {
             return new ConstantScoreQuery(MakeFilter(args));
@@ -132,7 +133,7 @@ namespace Lucene.Net.Spatial
         /// <code>return new QueryWrapperFilter(MakeQuery(args).Query);</code>
         /// </summary>
         /// <exception cref="NotSupportedException">If the strategy does not support the shape in <paramref name="args"/>.</exception>
-        /// <exception cref="UnsupportedSpatialOperation">If the strategy does not support the <see cref="SpatialOperation"/> in <paramref name="args"/>.</exception>
+        /// <exception cref="UnsupportedSpatialOperationException">If the strategy does not support the <see cref="SpatialOperation"/> in <paramref name="args"/>.</exception>
         public abstract Filter MakeFilter(SpatialArgs args);
 
         /// <summary>
@@ -145,8 +146,12 @@ namespace Lucene.Net.Spatial
         /// </summary>
         public ValueSource MakeRecipDistanceValueSource(IShape queryShape)
         {
+            // LUCENENET specific - added guard clause
+            if (queryShape is null)
+                throw new ArgumentNullException(nameof(queryShape));
+
             IRectangle bbox = queryShape.BoundingBox;
-            double diagonalDist = m_ctx.DistCalc.Distance(
+            double diagonalDist = m_ctx.DistanceCalculator.Distance(
                 m_ctx.MakePoint(bbox.MinX, bbox.MinY), bbox.MaxX, bbox.MaxY);
             double distToEdge = diagonalDist * 0.5;
             float c = (float)distToEdge * 0.1f; //one tenth
diff --git a/src/Lucene.Net.Spatial/Util/CachingDoubleValueSource.cs b/src/Lucene.Net.Spatial/Util/CachingDoubleValueSource.cs
index 015db2f..db3a0fa 100644
--- a/src/Lucene.Net.Spatial/Util/CachingDoubleValueSource.cs
+++ b/src/Lucene.Net.Spatial/Util/CachingDoubleValueSource.cs
@@ -1,5 +1,6 @@
 using Lucene.Net.Index;
 using Lucene.Net.Queries.Function;
+using System;
 using System.Collections;
 using System.Collections.Generic;
 using JCG = J2N.Collections.Generic;
@@ -24,8 +25,9 @@ namespace Lucene.Net.Spatial.Util
      */
 
     /// <summary>
-    /// Caches the doubleVal of another value source in a HashMap
+    /// Caches the doubleVal of another value source in a <see cref="Dictionary{TKey, TValue}"/>
     /// so that it is computed only once.
+    /// <para/>
     /// @lucene.internal
     /// </summary>
     public class CachingDoubleValueSource : ValueSource
@@ -35,7 +37,8 @@ namespace Lucene.Net.Spatial.Util
         
         public CachingDoubleValueSource(ValueSource source)
         {
-            this.m_source = source;
+            // LUCENENET specific - added guard clause
+            this.m_source = source ?? throw new ArgumentNullException(nameof(source));
             m_cache = new JCG.Dictionary<int, double>();
         }
 
@@ -46,6 +49,10 @@ namespace Lucene.Net.Spatial.Util
 
         public override FunctionValues GetValues(IDictionary context, AtomicReaderContext readerContext)
         {
+            // LUCENENET specific - added guard clause
+            if (readerContext is null)
+                throw new ArgumentNullException(nameof(readerContext));
+
             int @base = readerContext.DocBase;
             FunctionValues vals = m_source.GetValues(context, readerContext);
             return new CachingDoubleFunctionValue(@base, vals, m_cache);
@@ -61,9 +68,10 @@ namespace Lucene.Net.Spatial.Util
 
             public CachingDoubleFunctionValue(int docBase, FunctionValues vals, IDictionary<int, double> cache)
             {
+                // LUCENENET specific - added guard clauses
                 this.docBase = docBase;
-                values = vals;
-                this.cache = cache;
+                values = vals ?? throw new ArgumentNullException(nameof(vals));
+                this.cache = cache ?? throw new ArgumentNullException(nameof(cache));
             }
 
             public override double DoubleVal(int doc)
@@ -93,12 +101,10 @@ namespace Lucene.Net.Spatial.Util
 
         #endregion
 
-        public override bool Equals(object o)
+        public override bool Equals(object? o)
         {
             if (this == o) return true;
-
-
-            if (!(o is CachingDoubleValueSource that)) return false;
+            if (o is null || !(o is CachingDoubleValueSource that)) return false;
             if (m_source != null ? !m_source.Equals(that.m_source) : that.m_source != null) return false;
 
             return true;
diff --git a/src/Lucene.Net.Spatial/Util/DistanceToShapeValueSource.cs b/src/Lucene.Net.Spatial/Util/DistanceToShapeValueSource.cs
index 53a7d0e..500ef10 100644
--- a/src/Lucene.Net.Spatial/Util/DistanceToShapeValueSource.cs
+++ b/src/Lucene.Net.Spatial/Util/DistanceToShapeValueSource.cs
@@ -3,9 +3,10 @@ using Lucene.Net.Index;
 using Lucene.Net.Queries.Function;
 using Lucene.Net.Queries.Function.DocValues;
 using Lucene.Net.Search;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Distance;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Distance;
+using Spatial4n.Shapes;
+using System;
 using System.Collections;
 
 namespace Lucene.Net.Spatial.Util
@@ -47,10 +48,14 @@ namespace Lucene.Net.Spatial.Util
         public DistanceToShapeValueSource(ValueSource shapeValueSource, IPoint queryPoint,
                                           double multiplier, SpatialContext ctx)
         {
-            this.shapeValueSource = shapeValueSource;
-            this.queryPoint = queryPoint;
+            // LUCENENET specific - added guard clauses
+            this.shapeValueSource = shapeValueSource ?? throw new ArgumentNullException(nameof(shapeValueSource));
+            this.queryPoint = queryPoint ?? throw new ArgumentNullException(nameof(shapeValueSource));
+            if (ctx is null)
+                throw new ArgumentNullException(nameof(ctx));
+
             this.multiplier = multiplier;
-            this.distCalc = ctx.DistCalc;
+            this.distCalc = ctx.DistanceCalculator;
             this.nullValue =
                 (ctx.IsGeo ? 180 * multiplier : double.MaxValue);
         }
@@ -67,6 +72,10 @@ namespace Lucene.Net.Spatial.Util
 
         public override FunctionValues GetValues(IDictionary context, AtomicReaderContext readerContext)
         {
+            // LUCENENET specific - added guard clause
+            if (readerContext is null)
+                throw new ArgumentNullException(nameof(readerContext));
+
             FunctionValues shapeValues = shapeValueSource.GetValues(context, readerContext);
 
             return new DoubleDocValuesAnonymousClass(this, shapeValues);
@@ -80,8 +89,9 @@ namespace Lucene.Net.Spatial.Util
             public DoubleDocValuesAnonymousClass(DistanceToShapeValueSource outerInstance, FunctionValues shapeValues)
                 : base(outerInstance)
             {
-                this.outerInstance = outerInstance;
-                this.shapeValues = shapeValues;
+                // LUCENENET specific - added guard clauses
+                this.outerInstance = outerInstance ?? throw new ArgumentNullException(nameof(outerInstance));
+                this.shapeValues = shapeValues ?? throw new ArgumentNullException(nameof(shapeValues));
             }
 
             public override double DoubleVal(int doc)
@@ -101,7 +111,7 @@ namespace Lucene.Net.Spatial.Util
             }
         }
 
-        public override bool Equals(object o)
+        public override bool Equals(object? o)
         {
             if (this == o) return true;
             if (o is null || GetType() != o.GetType()) return false;
diff --git a/src/Lucene.Net.Spatial/Util/ShapeFieldCache.cs b/src/Lucene.Net.Spatial/Util/ShapeFieldCache.cs
index 076ac03..2dd173e 100644
--- a/src/Lucene.Net.Spatial/Util/ShapeFieldCache.cs
+++ b/src/Lucene.Net.Spatial/Util/ShapeFieldCache.cs
@@ -1,4 +1,5 @@
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
+using System;
 using System.Collections.Generic;
 using JCG = J2N.Collections.Generic;
 
@@ -38,12 +39,22 @@ namespace Lucene.Net.Spatial.Util
 
         public ShapeFieldCache(int length, int defaultLength)
         {
+            // LUCENENET specific - added guard clause
+            if (length < 0)
+                throw new ArgumentOutOfRangeException(nameof(length), $"{nameof(length)} must be greater than or equal to 0.");
+
             cache = new IList<T>[length];
             this.DefaultLength = defaultLength;
         }
 
         public virtual void Add(int docid, T s)
         {
+            // LUCENENET specific - added guard clauses
+            if (s is null)
+                throw new ArgumentNullException(nameof(s));
+            if (docid < 0 || docid >= cache.Length)
+                throw new ArgumentOutOfRangeException(nameof(docid), $"{nameof(docid)} must be positive and less than {Math.Max(cache.Length - 1, 0)}.");
+
             IList<T> list = cache[docid];
             if (list is null)
             {
@@ -54,6 +65,10 @@ namespace Lucene.Net.Spatial.Util
 
         public virtual IList<T> GetShapes(int docid)
         {
+            // LUCENENET specific - added guard clause
+            if (docid < 0 || docid >= cache.Length)
+                throw new ArgumentOutOfRangeException(nameof(docid), $"{nameof(docid)} must be positive and less than {Math.Max(cache.Length - 1, 0)}.");
+
             return cache[docid];
         }
     }
diff --git a/src/Lucene.Net.Spatial/Util/ShapeFieldCacheDistanceValueSource.cs b/src/Lucene.Net.Spatial/Util/ShapeFieldCacheDistanceValueSource.cs
index d334c23..bfed357 100644
--- a/src/Lucene.Net.Spatial/Util/ShapeFieldCacheDistanceValueSource.cs
+++ b/src/Lucene.Net.Spatial/Util/ShapeFieldCacheDistanceValueSource.cs
@@ -1,8 +1,8 @@
 using Lucene.Net.Index;
 using Lucene.Net.Queries.Function;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Distance;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Distance;
+using Spatial4n.Shapes;
 using System;
 using System.Collections;
 
@@ -43,9 +43,10 @@ namespace Lucene.Net.Spatial.Util
         public ShapeFieldCacheDistanceValueSource(SpatialContext ctx, 
             ShapeFieldCacheProvider<IPoint> provider, IPoint from, double multiplier)
         {
-            this.ctx = ctx;
-            this.from = from;
-            this.provider = provider;
+            // LUCENENET specific - added guard clauses
+            this.ctx = ctx ?? throw new ArgumentNullException(nameof(ctx));
+            this.from = from ?? throw new ArgumentNullException(nameof(from));
+            this.provider = provider ?? throw new ArgumentNullException(nameof(provider));
             this.multiplier = multiplier;
         }
 
@@ -56,25 +57,31 @@ namespace Lucene.Net.Spatial.Util
 
         public override FunctionValues GetValues(IDictionary context, AtomicReaderContext readerContext)
         {
-            return new CachedDistanceFunctionValue(readerContext.AtomicReader, this);
+            // LUCENENET specific - added guard clause
+            if (readerContext is null)
+                throw new ArgumentNullException(nameof(readerContext));
+
+            return new CachedDistanceFunctionValue(this, readerContext.AtomicReader);
         }
 
         internal class CachedDistanceFunctionValue : FunctionValues
         {
-            private readonly ShapeFieldCacheDistanceValueSource enclosingInstance;
+            private readonly ShapeFieldCacheDistanceValueSource outerInstance;
             private readonly ShapeFieldCache<IPoint> cache;
             private readonly IPoint from;
             private readonly IDistanceCalculator calculator;
             private readonly double nullValue;
 
-            public CachedDistanceFunctionValue(AtomicReader reader, ShapeFieldCacheDistanceValueSource enclosingInstance)
+            public CachedDistanceFunctionValue(ShapeFieldCacheDistanceValueSource outerInstance, AtomicReader reader)
             {
-                cache = enclosingInstance.provider.GetCache(reader);
-                this.enclosingInstance = enclosingInstance;
-
-                from = enclosingInstance.from;
-                calculator = enclosingInstance.ctx.DistCalc;
-                nullValue = (enclosingInstance.ctx.IsGeo ? 180 * enclosingInstance.multiplier : double.MaxValue);
+                // LUCENENET specific - added guard clauses
+                this.outerInstance = outerInstance ?? throw new ArgumentNullException(nameof(outerInstance));
+                if (reader is null)
+                    throw new ArgumentNullException(nameof(reader));
+                cache = outerInstance.provider.GetCache(reader);
+                from = outerInstance.from;
+                calculator = outerInstance.ctx.DistanceCalculator;
+                nullValue = (outerInstance.ctx.IsGeo ? 180 * outerInstance.multiplier : double.MaxValue);
             }
 
             /// <summary>
@@ -95,23 +102,22 @@ namespace Lucene.Net.Spatial.Util
                     {
                         v = Math.Min(v, calculator.Distance(from, vals[i]));
                     }
-                    return v * enclosingInstance.multiplier;
+                    return v * outerInstance.multiplier;
                 }
                 return nullValue;
             }
 
             public override string ToString(int doc)
             {
-                return enclosingInstance.GetDescription() + "=" + SingleVal(doc);
+                return outerInstance.GetDescription() + "=" + SingleVal(doc);
             }
         }
 
-        public override bool Equals(object o)
+        public override bool Equals(object? o)
         {
             if (this == o) return true;
             if (o is null || GetType() != o.GetType()) return false;
 
-
             if (!(o is ShapeFieldCacheDistanceValueSource that)) return false;
             if (!ctx.Equals(that.ctx)) return false;
             if (!from.Equals(that.from)) return false;
diff --git a/src/Lucene.Net.Spatial/Util/ShapeFieldCacheProvider.cs b/src/Lucene.Net.Spatial/Util/ShapeFieldCacheProvider.cs
index 117b809..31a4b92 100644
--- a/src/Lucene.Net.Spatial/Util/ShapeFieldCacheProvider.cs
+++ b/src/Lucene.Net.Spatial/Util/ShapeFieldCacheProvider.cs
@@ -1,8 +1,9 @@
 using Lucene.Net.Index;
 using Lucene.Net.Search;
 using Lucene.Net.Util;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
 using System;
+using System.Diagnostics.CodeAnalysis;
 using System.Runtime.CompilerServices;
 
 namespace Lucene.Net.Spatial.Util
@@ -25,16 +26,15 @@ namespace Lucene.Net.Spatial.Util
      */
 
     /// <summary>
-    /// Provides access to a
-    /// <see cref="ShapeFieldCache{T}">ShapeFieldCache&lt;T&gt;</see>
-    /// for a given
-    /// <see cref="Lucene.Net.Index.AtomicReader">Lucene.Net.Index.AtomicReader</see>.
-    /// 
-    /// If a Cache does not exist for the TextReader, then it is built by iterating over
+    /// Provides access to a <see cref="ShapeFieldCache{T}" />
+    /// for a given <see cref="AtomicReader" />.
+    /// <para/>
+    /// If a Cache does not exist for the Reader, then it is built by iterating over
     /// the all terms for a given field, reconstructing the Shape from them, and adding
     /// them to the Cache.
-    /// </summary>
+    /// <para/>
     /// @lucene.internal
+    /// </summary>
     public abstract class ShapeFieldCacheProvider<T>
         where T : IShape
     {
@@ -45,17 +45,18 @@ namespace Lucene.Net.Spatial.Util
         private readonly ConditionalWeakTable<IndexReader, Lazy<ShapeFieldCache<T>>> sidx =
             new ConditionalWeakTable<IndexReader, Lazy<ShapeFieldCache<T>>>();
 
-        protected internal readonly int m_defaultSize;
-        protected internal readonly string m_shapeField;
+        protected readonly int m_defaultSize;
+        protected readonly string m_shapeField;
 
         protected ShapeFieldCacheProvider(string shapeField, int defaultSize) // LUCENENET: CA1012: Abstract types should not have constructors (marked protected)
         {
             // it may be a List<T> or T
-            this.m_shapeField = shapeField;
+            this.m_shapeField = shapeField ?? throw new ArgumentNullException(nameof(shapeField)); // LUCENENET specific - added guard clause
             this.m_defaultSize = defaultSize;
         }
 
-        protected internal abstract T ReadShape(BytesRef term);
+        [return: MaybeNull]
+        protected abstract T ReadShape(BytesRef term);
 
         public virtual ShapeFieldCache<T> GetCache(AtomicReader reader)
         {
@@ -67,15 +68,15 @@ namespace Lucene.Net.Spatial.Util
                 log.Fine("Building Cache [" + reader.MaxDoc() + "]");*/
                 ShapeFieldCache<T> idx = new ShapeFieldCache<T>(key.MaxDoc, m_defaultSize);
                 int count = 0;
-                DocsEnum docs = null;
+                DocsEnum? docs = null;
                 Terms terms = ((AtomicReader)key).GetTerms(m_shapeField);
-                TermsEnum te = null;
+                TermsEnum? te = null;
                 if (terms != null)
                 {
                     te = terms.GetEnumerator(te);
                     while (te.MoveNext())
                     {
-                        T shape = ReadShape(te.Term);
+                        T? shape = ReadShape(te.Term);
                         if (shape != null)
                         {
                             docs = te.Docs(null, docs, DocsFlags.NONE);
diff --git a/src/Lucene.Net.Spatial/Util/ShapePredicateValueSource.cs b/src/Lucene.Net.Spatial/Util/ShapePredicateValueSource.cs
index 383a4a6..c2cb55b 100644
--- a/src/Lucene.Net.Spatial/Util/ShapePredicateValueSource.cs
+++ b/src/Lucene.Net.Spatial/Util/ShapePredicateValueSource.cs
@@ -3,7 +3,8 @@ using Lucene.Net.Queries.Function;
 using Lucene.Net.Queries.Function.DocValues;
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Queries;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Shapes;
+using System;
 using System.Collections;
 
 namespace Lucene.Net.Spatial.Util
@@ -26,48 +27,49 @@ namespace Lucene.Net.Spatial.Util
      */
 
     /// <summary>
-    /// A boolean <see cref="ValueSource"/> that compares a shape from a provided <see cref="ValueSource"/> with a given <see cref="IShape">Shape</see> and sees
-    /// if it matches a given <see cref="SpatialOperation"/> (the predicate).
-    /// 
+    /// A boolean <see cref="ValueSource" /> that compares a shape from a provided <see cref="ValueSource" /> with a given <see cref="IShape" /> and sees
+    /// if it matches a given <see cref="SpatialOperation" /> (the predicate).
+    /// <para/>
     /// @lucene.experimental
     /// </summary>
     public class ShapePredicateValueSource : ValueSource
     {
-        private readonly ValueSource shapeValuesource;//the left hand side
+        private readonly ValueSource shapeValueSource;//the left hand side
         private readonly SpatialOperation op;
         private readonly IShape queryShape;//the right hand side (constant)
 
         /// <summary>
         /// 
         /// </summary>
-        /// <param name="shapeValuesource">
+        /// <param name="shapeValueSource">
         /// Must yield <see cref="IShape"/> instances from it's ObjectVal(doc). If null
         /// then the result is false. This is the left-hand (indexed) side.
         /// </param>
         /// <param name="op">the predicate</param>
         /// <param name="queryShape">The shape on the right-hand (query) side.</param>
-        public ShapePredicateValueSource(ValueSource shapeValuesource, SpatialOperation op, IShape queryShape)
+        public ShapePredicateValueSource(ValueSource shapeValueSource, SpatialOperation op, IShape queryShape)
         {
-            this.shapeValuesource = shapeValuesource;
-            this.op = op;
-            this.queryShape = queryShape;
+            // LUCENENET specific - added guard clauses
+            this.shapeValueSource = shapeValueSource ?? throw new ArgumentNullException(nameof(shapeValueSource));
+            this.op = op ?? throw new ArgumentNullException(nameof(op));
+            this.queryShape = queryShape ?? throw new ArgumentNullException(nameof(queryShape));
         }
 
 
         public override string GetDescription()
         {
-            return shapeValuesource + " " + op + " " + queryShape;
+            return shapeValueSource + " " + op + " " + queryShape;
         }
 
 
         public override void CreateWeight(IDictionary context, IndexSearcher searcher)
         {
-            shapeValuesource.CreateWeight(context, searcher);
+            shapeValueSource.CreateWeight(context, searcher);
         }
 
         public override FunctionValues GetValues(IDictionary context, AtomicReaderContext readerContext)
         {
-            FunctionValues shapeValues = shapeValuesource.GetValues(context, readerContext);
+            FunctionValues shapeValues = shapeValueSource.GetValues(context, readerContext);
 
             return new BoolDocValuesAnonymousClass(this, shapeValues);
         }
@@ -80,8 +82,9 @@ namespace Lucene.Net.Spatial.Util
             public BoolDocValuesAnonymousClass(ShapePredicateValueSource outerInstance, FunctionValues shapeValues)
                 : base(outerInstance)
             {
-                this.outerInstance = outerInstance;
-                this.shapeValues = shapeValues;
+                // LUCENENET specific - added guard clauses
+                this.outerInstance = outerInstance ?? throw new ArgumentNullException(nameof(outerInstance));
+                this.shapeValues = shapeValues ?? throw new ArgumentNullException(nameof(shapeValues));
             }
 
             public override bool BoolVal(int doc)
@@ -100,14 +103,14 @@ namespace Lucene.Net.Spatial.Util
             }
         }
 
-        public override bool Equals(object o)
+        public override bool Equals(object? o)
         {
             if (this == o) return true;
             if (o is null || GetType() != o.GetType()) return false;
 
             ShapePredicateValueSource that = (ShapePredicateValueSource)o;
 
-            if (!shapeValuesource.Equals(that.shapeValuesource)) return false;
+            if (!shapeValueSource.Equals(that.shapeValueSource)) return false;
             if (!op.Equals(that.op)) return false;
             if (!queryShape.Equals(that.queryShape)) return false;
 
@@ -116,7 +119,7 @@ namespace Lucene.Net.Spatial.Util
 
         public override int GetHashCode()
         {
-            int result = shapeValuesource.GetHashCode();
+            int result = shapeValueSource.GetHashCode();
             result = 31 * result + op.GetHashCode();
             result = 31 * result + queryShape.GetHashCode();
             return result;
diff --git a/src/Lucene.Net.Spatial/Util/ValueSourceFilter.cs b/src/Lucene.Net.Spatial/Util/ValueSourceFilter.cs
index c3c3bb7..8f6caf2 100644
--- a/src/Lucene.Net.Spatial/Util/ValueSourceFilter.cs
+++ b/src/Lucene.Net.Spatial/Util/ValueSourceFilter.cs
@@ -25,7 +25,8 @@ namespace Lucene.Net.Spatial.Util
 
     /// <summary>
     /// <see cref="Filter"/> that matches all documents where a <see cref="ValueSource"/> is
-    /// in between a range of <c>min</c> and <c>max</c> inclusive.
+    /// in between a range of <see cref="Min"/> and <see cref="Max"/> inclusive.
+    /// <para/>
     /// @lucene.internal
     /// </summary>
     public class ValueSourceFilter : Filter
@@ -45,12 +46,12 @@ namespace Lucene.Net.Spatial.Util
             // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
             this.startingFilter = startingFilter ?? throw new ArgumentNullException(nameof(startingFilter),
                 "Please provide a non-null startingFilter; you can use QueryWrapperFilter(MatchAllDocsQuery) as a no-op filter");
-            this.source = source;
+            this.source = source ?? throw new ArgumentNullException(nameof(source)); // LUCENENET specific - added guard clause
             this.min = min;
             this.max = max;
         }
 
-        public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
+        public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits? acceptDocs)
         {
             var values = source.GetValues(null, context);
             return new ValueSourceFilteredDocIdSet(this, startingFilter.GetDocIdSet(context, acceptDocs), values);
@@ -61,11 +62,12 @@ namespace Lucene.Net.Spatial.Util
             private readonly ValueSourceFilter outerInstance;
             private readonly FunctionValues values;
 
-            public ValueSourceFilteredDocIdSet(ValueSourceFilter outerInstance, DocIdSet innerSet, FunctionValues values)
+            public ValueSourceFilteredDocIdSet(ValueSourceFilter outerInstance, DocIdSet? innerSet, FunctionValues values)
                 : base(innerSet)
             {
-                this.outerInstance = outerInstance;
-                this.values = values;
+                // LUCENENET specific - added guard clauses
+                this.outerInstance = outerInstance ?? throw new ArgumentNullException(nameof(outerInstance));
+                this.values = values ?? throw new ArgumentNullException(nameof(values));
             }
 
             protected override bool Match(int doc)
diff --git a/src/Lucene.Net.Spatial/Vector/DistanceValueSource.cs b/src/Lucene.Net.Spatial/Vector/DistanceValueSource.cs
index 7d69c10..cdd96f9 100644
--- a/src/Lucene.Net.Spatial/Vector/DistanceValueSource.cs
+++ b/src/Lucene.Net.Spatial/Vector/DistanceValueSource.cs
@@ -3,10 +3,10 @@ using Lucene.Net.Index;
 using Lucene.Net.Queries.Function;
 using Lucene.Net.Search;
 using Lucene.Net.Util;
-using Spatial4n.Core.Distance;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Distance;
+using Spatial4n.Shapes;
+using System;
 using System.Collections;
-using System.Diagnostics;
 
 namespace Lucene.Net.Spatial.Vector
 {
@@ -30,7 +30,7 @@ namespace Lucene.Net.Spatial.Vector
     /// <summary>
     /// An implementation of the Lucene <see cref="ValueSource"/> model that returns the distance
     /// for a <see cref="PointVectorStrategy"/>.
-    /// 
+    /// <para/>
     /// @lucene.internal
     /// </summary>
     public class DistanceValueSource : ValueSource
@@ -42,10 +42,12 @@ namespace Lucene.Net.Spatial.Vector
         /// <summary>
         /// Constructor.
         /// </summary>
+        /// <exception cref="ArgumentNullException"><paramref name="strategy"/> or <paramref name="from"/> is <c>null</c>.</exception>
         public DistanceValueSource(PointVectorStrategy strategy, IPoint from, double multiplier)
         {
-            this.strategy = strategy;
-            this.from = from;
+            // LUCENENET specific - added guard clauses
+            this.strategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
+            this.from = from ?? throw new ArgumentNullException(nameof(from));
             this.multiplier = multiplier;
         }
 
@@ -62,6 +64,10 @@ namespace Lucene.Net.Spatial.Vector
         /// </summary>
         public override FunctionValues GetValues(IDictionary context, AtomicReaderContext readerContext)
         {
+            // LUCENENET specific - added guard clause
+            if (readerContext is null)
+                throw new ArgumentNullException(nameof(readerContext));
+
             return new DistanceFunctionValue(this, readerContext.AtomicReader);
         }
 
@@ -79,7 +85,10 @@ namespace Lucene.Net.Spatial.Vector
 
             public DistanceFunctionValue(DistanceValueSource outerInstance, AtomicReader reader)
             {
-                this.outerInstance = outerInstance;
+                // LUCENENET specific - added guard clauses
+                this.outerInstance = outerInstance ?? throw new ArgumentNullException(nameof(outerInstance));
+                if (reader is null)
+                    throw new ArgumentNullException(nameof(reader));
 
                 ptX = FieldCache.DEFAULT.GetDoubles(reader, outerInstance.strategy.FieldNameX, true);
                 ptY = FieldCache.DEFAULT.GetDoubles(reader, outerInstance.strategy.FieldNameY, true);
@@ -87,7 +96,7 @@ namespace Lucene.Net.Spatial.Vector
                 validY = FieldCache.DEFAULT.GetDocsWithField(reader, outerInstance.strategy.FieldNameY);
 
                 //from = outerInstance.from; // LUCENENET: Never read
-                calculator = outerInstance.strategy.SpatialContext.DistCalc;
+                calculator = outerInstance.strategy.SpatialContext.DistanceCalculator;
                 nullValue = (outerInstance.strategy.SpatialContext.IsGeo ? 180 * outerInstance.multiplier : double.MaxValue);
             }
 
@@ -118,7 +127,7 @@ namespace Lucene.Net.Spatial.Vector
 
         #endregion
 
-        public override bool Equals(object o)
+        public override bool Equals(object? o)
         {
             if (this == o) return true;
             if (o is null || GetType() != o.GetType()) return false;
diff --git a/src/Lucene.Net.Spatial/Vector/PointVectorStrategy.cs b/src/Lucene.Net.Spatial/Vector/PointVectorStrategy.cs
index 02bc60a..8a72100 100644
--- a/src/Lucene.Net.Spatial/Vector/PointVectorStrategy.cs
+++ b/src/Lucene.Net.Spatial/Vector/PointVectorStrategy.cs
@@ -3,8 +3,8 @@ using Lucene.Net.Queries.Function;
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Queries;
 using Lucene.Net.Spatial.Util;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 
 namespace Lucene.Net.Spatial.Vector
@@ -48,13 +48,13 @@ namespace Lucene.Net.Spatial.Vector
     /// both a search using a Circle and sort will result in calculations for the
     /// spatial distance being done twice -- once for the filter and second for the
     /// sort.
-    /// 
+    /// <para/>
     /// @lucene.experimental
     /// </summary>
     public class PointVectorStrategy : SpatialStrategy
     {
-        public static string SUFFIX_X = "__x";
-        public static string SUFFIX_Y = "__y";
+        public const string SUFFIX_X = "__x";
+        public const string SUFFIX_Y = "__y";
 
         private readonly string fieldNameX;
         private readonly string fieldNameY;
@@ -85,6 +85,10 @@ namespace Lucene.Net.Spatial.Vector
 
         public override Field[] CreateIndexableFields(IShape shape)
         {
+            // LUCENENET specific - added guard clause
+            if (shape is null)
+                throw new ArgumentNullException(nameof(shape));
+
             if (shape is IPoint point)
                 return CreateIndexableFields(point);
 
@@ -96,6 +100,10 @@ namespace Lucene.Net.Spatial.Vector
         /// </summary>
         public virtual Field[] CreateIndexableFields(IPoint point)
         {
+            // LUCENENET specific - added guard clause
+            if (point is null)
+                throw new ArgumentNullException(nameof(point));
+
             FieldType doubleFieldType = new FieldType(DoubleField.TYPE_NOT_STORED)
             {
                 NumericPrecisionStep = precisionStep
@@ -115,6 +123,10 @@ namespace Lucene.Net.Spatial.Vector
 
         public override Filter MakeFilter(SpatialArgs args)
         {
+            // LUCENENET specific - added guard clause
+            if (args is null)
+                throw new ArgumentNullException(nameof(args));
+
             //unwrap the CSQ from makeQuery
             ConstantScoreQuery csq = MakeQuery(args);
             Filter filter = csq.Filter;
@@ -126,11 +138,15 @@ namespace Lucene.Net.Spatial.Vector
 
         public override ConstantScoreQuery MakeQuery(SpatialArgs args)
         {
+            // LUCENENET specific - added guard clause
+            if (args is null)
+                throw new ArgumentNullException(nameof(args));
+
             if (!SpatialOperation.Is(args.Operation,
                 SpatialOperation.Intersects,
                 SpatialOperation.IsWithin))
             {
-                throw new UnsupportedSpatialOperation(args.Operation);
+                throw new UnsupportedSpatialOperationException(args.Operation);
             }
 
             IShape shape = args.Shape;
@@ -157,6 +173,10 @@ namespace Lucene.Net.Spatial.Vector
         //TODO this is basically old code that hasn't been verified well and should probably be removed
         public virtual Query MakeQueryDistanceScore(SpatialArgs args)
         {
+            // LUCENENET specific - added guard clause
+            if (args is null)
+                throw new ArgumentNullException(nameof(args));
+
             // For starters, just limit the bbox
             var shape = args.Shape;
             if (!(shape is IRectangle || shape is ICircle))
@@ -169,9 +189,9 @@ namespace Lucene.Net.Spatial.Vector
                 throw UnsupportedOperationException.Create("Crossing dateline not yet supported");
             }
 
-            ValueSource valueSource = null;
+            ValueSource? valueSource = null;
 
-            Query spatial = null;
+            Query? spatial = null;
             SpatialOperation op = args.Operation;
 
             if (SpatialOperation.Is(op,
@@ -203,7 +223,7 @@ namespace Lucene.Net.Spatial.Vector
 
             if (spatial is null)
             {
-                throw new UnsupportedSpatialOperation(args.Operation);
+                throw new UnsupportedSpatialOperationException(args.Operation);
             }
 
             if (valueSource != null)
@@ -227,6 +247,10 @@ namespace Lucene.Net.Spatial.Vector
         /// </summary>
         private Query MakeWithin(IRectangle bbox)
         {
+            // LUCENENET specific - added guard clause
+            if (bbox is null)
+                throw new ArgumentNullException(nameof(bbox));
+
             var bq = new BooleanQuery();
             const Occur MUST = Occur.MUST;
             if (bbox.CrossesDateLine)
@@ -260,6 +284,10 @@ namespace Lucene.Net.Spatial.Vector
         /// </summary>
         private Query MakeDisjoint(IRectangle bbox)
         {
+            // LUCENENET specific - added guard clause
+            if (bbox is null)
+                throw new ArgumentNullException(nameof(bbox));
+
             if (bbox.CrossesDateLine)
                 throw UnsupportedOperationException.Create("MakeDisjoint doesn't handle dateline cross");
             Query qX = RangeQuery(fieldNameX, bbox.MinX, bbox.MaxX);
diff --git a/src/Lucene.Net.Tests.AllProjects/Support/ExceptionHandling/ExceptionScanningTestCase.cs b/src/Lucene.Net.Tests.AllProjects/Support/ExceptionHandling/ExceptionScanningTestCase.cs
index d352559..9c51aae 100644
--- a/src/Lucene.Net.Tests.AllProjects/Support/ExceptionHandling/ExceptionScanningTestCase.cs
+++ b/src/Lucene.Net.Tests.AllProjects/Support/ExceptionHandling/ExceptionScanningTestCase.cs
@@ -318,7 +318,7 @@ namespace Lucene.Net.Support.ExceptionHandling
                 typeof(J2N.IO.BufferUnderflowException),
                 typeof(J2N.IO.BufferOverflowException),
                 typeof(J2N.IO.InvalidMarkException),
-                typeof(Lucene.Net.Spatial.Queries.UnsupportedSpatialOperation), // Subclasses NotSupportedException
+                typeof(Lucene.Net.Spatial.Queries.UnsupportedSpatialOperationException), // Subclasses NotSupportedException
 
                 //typeof(NUnit.Framework.Internal.InvalidPlatformException),
 
diff --git a/src/Lucene.Net.Tests.Spatial/DistanceStrategyTest.cs b/src/Lucene.Net.Tests.Spatial/DistanceStrategyTest.cs
index 0427f38..9da233f 100644
--- a/src/Lucene.Net.Tests.Spatial/DistanceStrategyTest.cs
+++ b/src/Lucene.Net.Tests.Spatial/DistanceStrategyTest.cs
@@ -5,8 +5,8 @@ using Lucene.Net.Spatial.Vector;
 using Lucene.Net.Support;
 using NUnit.Framework;
 using RandomizedTesting.Generators;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
 using JCG = J2N.Collections.Generic;
@@ -37,7 +37,7 @@ namespace Lucene.Net.Spatial
         {
             IList<Object[]> ctorArgs = new JCG.List<object[]>();
 
-            SpatialContext ctx = SpatialContext.GEO;
+            SpatialContext ctx = SpatialContext.Geo;
             SpatialPrefixTree grid;
             SpatialStrategy strategy;
 
@@ -126,7 +126,7 @@ namespace Lucene.Net.Spatial
             DeleteDoc("999");
             Commit();
 
-            double dist = ctx.DistCalc.Distance(p100, p101);
+            double dist = ctx.DistanceCalculator.Distance(p100, p101);
             IShape queryShape = ctx.MakeCircle(2.01, 0.99, dist);
             CheckValueSource(strategy.MakeRecipDistanceValueSource(queryShape),
             new float[] { 1.00f, 0.10f, 0f }, 0.09f);
diff --git a/src/Lucene.Net.Tests.Spatial/Lucene.Net.Tests.Spatial.csproj b/src/Lucene.Net.Tests.Spatial/Lucene.Net.Tests.Spatial.csproj
index adc1701..e4dd17c 100644
--- a/src/Lucene.Net.Tests.Spatial/Lucene.Net.Tests.Spatial.csproj
+++ b/src/Lucene.Net.Tests.Spatial/Lucene.Net.Tests.Spatial.csproj
@@ -55,8 +55,7 @@
   <Import Project="$(SolutionDir).build/TestReferences.Common.targets" />
 
   <ItemGroup>
-    <PackageReference Include="Spatial4n.Core" Version="$(Spatial4nCorePackageVersion)" />
-    <PackageReference Include="Spatial4n.Core.NTS" Version="$(Spatial4nCoreNTSPackageVersion)" />
+    <PackageReference Include="Spatial4n" Version="$(Spatial4nPackageVersion)" />
   </ItemGroup>
 
 </Project>
diff --git a/src/Lucene.Net.Tests.Spatial/PortedSolr3Test.cs b/src/Lucene.Net.Tests.Spatial/PortedSolr3Test.cs
index 5706bc5..d2c847f 100644
--- a/src/Lucene.Net.Tests.Spatial/PortedSolr3Test.cs
+++ b/src/Lucene.Net.Tests.Spatial/PortedSolr3Test.cs
@@ -5,9 +5,9 @@ using Lucene.Net.Spatial.Queries;
 using Lucene.Net.Spatial.Vector;
 using NUnit.Framework;
 using RandomizedTesting.Generators;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Distance;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Distance;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
@@ -42,7 +42,7 @@ namespace Lucene.Net.Spatial
         {
             IList<Object[]> ctorArgs = new JCG.List<object[]>();
 
-            SpatialContext ctx = SpatialContext.GEO;
+            SpatialContext ctx = SpatialContext.Geo;
             SpatialPrefixTree grid;
             SpatialStrategy strategy;
 
@@ -167,7 +167,7 @@ namespace Lucene.Net.Spatial
         private void _CheckHits(bool bbox, IPoint pt, double distKM, int assertNumFound, params int[] assertIds)
         {
             SpatialOperation op = SpatialOperation.Intersects;
-            double distDEG = DistanceUtils.Dist2Degrees(distKM, DistanceUtils.EARTH_MEAN_RADIUS_KM);
+            double distDEG = DistanceUtils.Dist2Degrees(distKM, DistanceUtils.EarthMeanRadiusKilometers);
             IShape shape = ctx.MakeCircle(pt, distDEG);
             if (bbox)
                 shape = shape.BoundingBox;
diff --git a/src/Lucene.Net.Tests.Spatial/Prefix/NtsPolygonTest.cs b/src/Lucene.Net.Tests.Spatial/Prefix/NtsPolygonTest.cs
index 63c39c0..884d4c2 100644
--- a/src/Lucene.Net.Tests.Spatial/Prefix/NtsPolygonTest.cs
+++ b/src/Lucene.Net.Tests.Spatial/Prefix/NtsPolygonTest.cs
@@ -4,8 +4,9 @@ using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Spatial.Queries;
 using Lucene.Net.Support;
 using NUnit.Framework;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Context.Nts;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
 using Console = Lucene.Net.Util.SystemConsole;
@@ -37,14 +38,15 @@ namespace Lucene.Net.Spatial.Prefix
         {
             try
             {
-                IDictionary<string, string> args = new Dictionary<string, string>();
-                args.Put("spatialContextFactory",
-                    typeof(Spatial4n.Core.Context.Nts.NtsSpatialContextFactory).AssemblyQualifiedName);
-                ctx = SpatialContextFactory.MakeSpatialContext(args /*, getClass().getClassLoader()*/);
+                IDictionary<string, string> args = new Dictionary<string, string>
+                {
+                    ["SpatialContextFactory"] = typeof(NtsSpatialContextFactory).FullName
+                };
+                ctx = SpatialContextFactory.MakeSpatialContext(args, GetType().Assembly);
             }
             catch (Exception e) when (e.IsNoClassDefFoundError())
             {
-                AssumeTrue("This test requires Spatial4n.Core.NTS: " + e, false);
+                AssumeTrue("This test requires Spatial4n: " + e, false);
             }
 
             GeohashPrefixTree grid = new GeohashPrefixTree(ctx, 11);//< 1 meter == 11 maxLevels
diff --git a/src/Lucene.Net.Tests.Spatial/Prefix/SpatialOpRecursivePrefixTreeTest.cs b/src/Lucene.Net.Tests.Spatial/Prefix/SpatialOpRecursivePrefixTreeTest.cs
index 84afa25..926b274 100644
--- a/src/Lucene.Net.Tests.Spatial/Prefix/SpatialOpRecursivePrefixTreeTest.cs
+++ b/src/Lucene.Net.Tests.Spatial/Prefix/SpatialOpRecursivePrefixTreeTest.cs
@@ -4,9 +4,8 @@ using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Spatial.Queries;
 using Lucene.Net.Support;
 using NUnit.Framework;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
-using Spatial4n.Core.Shapes.Impl;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -63,20 +62,24 @@ namespace Lucene.Net.Spatial.Prefix
             if (!ctx.IsGeo)
                 ctx2D = ctx;
             //A non-geo version of ctx.
-            FakeSpatialContextFactory ctxFactory = new FakeSpatialContextFactory();
-            ctxFactory.geo = false;
-            ctxFactory.worldBounds = ctx.WorldBounds;
-            ctx2D = ctxFactory.NewSpatialContext();
+            SpatialContextFactory ctxFactory = new SpatialContextFactory
+            {
+                IsGeo = false,
+                WorldBounds = ctx.WorldBounds
+            };
+            ctx2D = ctxFactory.CreateSpatialContext();
         }
 
         private void SetupQuadGrid(int maxLevels)
         {
             //non-geospatial makes this test a little easier (in gridSnap), and using boundary values 2^X raises
             // the prospect of edge conditions we want to test, plus makes for simpler numbers (no decimals).
-            FakeSpatialContextFactory factory = new FakeSpatialContextFactory();
-            factory.geo = false;
-            factory.worldBounds = new Rectangle(0, 256, -128, 128, null);
-            this.ctx = factory.NewSpatialContext();
+            SpatialContextFactory factory = new SpatialContextFactory
+            {
+                IsGeo = false,
+                WorldBounds = new Rectangle(0, 256, -128, 128, null)
+            };
+            this.ctx = factory.CreateSpatialContext();
             //A fairly shallow grid, and default 2.5% distErrPct
             if (maxLevels == -1)
                 maxLevels = randomIntBetween(1, 8);//max 64k cells (4^8), also 256*256
@@ -84,22 +87,9 @@ namespace Lucene.Net.Spatial.Prefix
             this.strategy = new RecursivePrefixTreeStrategy(grid, GetType().Name);
         }
 
-        /// <summary>
-        /// LUCENENET specific class used to gain access to protected internal
-        /// member NewSpatialContext(), since we are not strong-named and
-        /// InternalsVisibleTo is not an option from a strong-named class.
-        /// </summary>
-        private class FakeSpatialContextFactory : SpatialContextFactory
-        {
-            new public SpatialContext NewSpatialContext()
-            {
-                return base.NewSpatialContext();
-            }
-        }
-
         public virtual void SetupGeohashGrid(int maxLevels)
         {
-            this.ctx = SpatialContext.GEO;
+            this.ctx = SpatialContext.Geo;
             //A fairly shallow grid, and default 2.5% distErrPct
             if (maxLevels == -1)
                 maxLevels = randomIntBetween(1, 3);//max 16k cells (32^3)
@@ -190,12 +180,12 @@ namespace Lucene.Net.Spatial.Prefix
         [Test]
         public void TestShapePair()
         {
-            ctx = SpatialContext.GEO;
+            ctx = SpatialContext.Geo;
             SetupCtx2D(ctx);
 
             IShape leftShape = new ShapePair(ctx.MakeRectangle(-74, -56, -8, 1), ctx.MakeRectangle(-180, 134, -90, 90), true, ctx, ctx2D);
             IShape queryShape = ctx.MakeRectangle(-180, 180, -90, 90);
-            assertEquals(SpatialRelation.WITHIN, leftShape.Relate(queryShape));
+            assertEquals(SpatialRelation.Within, leftShape.Relate(queryShape));
         }
 
         //Override so we can index parts of a pair separately, resulting in the detailLevel
@@ -500,11 +490,11 @@ namespace Lucene.Net.Spatial.Prefix
             public override SpatialRelation Relate(IShape other)
             {
                 SpatialRelation r = RelateApprox(other);
-                if (r == SpatialRelation.DISJOINT)
+                if (r == SpatialRelation.Disjoint)
                     return r;
-                if (r == SpatialRelation.CONTAINS)
+                if (r == SpatialRelation.Contains)
                     return r;
-                if (r == SpatialRelation.WITHIN && !biasContainsThenWithin)
+                if (r == SpatialRelation.Within && !biasContainsThenWithin)
                     return r;
 
                 //See if the correct answer is actually Contains, when the indexed shapes are adjacent,
@@ -522,7 +512,7 @@ namespace Lucene.Net.Spatial.Prefix
                     && CornerContainsNonGeo(oRect.MinX, oRect.MaxY)
                     && CornerContainsNonGeo(oRect.MaxX, oRect.MinY)
                     && CornerContainsNonGeo(oRect.MaxX, oRect.MaxY))
-                    return SpatialRelation.CONTAINS;
+                    return SpatialRelation.Contains;
                 return r;
             }
 
@@ -536,23 +526,23 @@ namespace Lucene.Net.Spatial.Prefix
             {
                 if (biasContainsThenWithin)
                 {
-                    if (shape1.Relate(other) == SpatialRelation.CONTAINS || shape1.equals(other)
-                        || shape2.Relate(other) == SpatialRelation.CONTAINS || shape2.equals(other)) return SpatialRelation.CONTAINS;
+                    if (shape1.Relate(other) == SpatialRelation.Contains || shape1.equals(other)
+                        || shape2.Relate(other) == SpatialRelation.Contains || shape2.equals(other)) return SpatialRelation.Contains;
 
-                    if (shape1.Relate(other) == SpatialRelation.WITHIN && shape2.Relate(other) == SpatialRelation.WITHIN) return SpatialRelation.WITHIN;
+                    if (shape1.Relate(other) == SpatialRelation.Within && shape2.Relate(other) == SpatialRelation.Within) return SpatialRelation.Within;
 
                 }
                 else
                 {
-                    if ((shape1.Relate(other) == SpatialRelation.WITHIN || shape1.equals(other))
-                        && (shape2.Relate(other) == SpatialRelation.WITHIN || shape2.equals(other))) return SpatialRelation.WITHIN;
+                    if ((shape1.Relate(other) == SpatialRelation.Within || shape1.equals(other))
+                        && (shape2.Relate(other) == SpatialRelation.Within || shape2.equals(other))) return SpatialRelation.Within;
 
-                    if (shape1.Relate(other) == SpatialRelation.CONTAINS || shape2.Relate(other) == SpatialRelation.CONTAINS) return SpatialRelation.CONTAINS;
+                    if (shape1.Relate(other) == SpatialRelation.Contains || shape2.Relate(other) == SpatialRelation.Contains) return SpatialRelation.Contains;
                 }
 
                 if (shape1.Relate(other).Intersects() || shape2.Relate(other).Intersects())
-                    return SpatialRelation.INTERSECTS;//might actually be 'CONTAINS' if the pair are adjacent but we handle that later
-                return SpatialRelation.DISJOINT;
+                    return SpatialRelation.Intersects;//might actually be 'CONTAINS' if the pair are adjacent but we handle that later
+                return SpatialRelation.Disjoint;
             }
 
             public override String ToString()
diff --git a/src/Lucene.Net.Tests.Spatial/Prefix/TestRecursivePrefixTreeStrategy.cs b/src/Lucene.Net.Tests.Spatial/Prefix/TestRecursivePrefixTreeStrategy.cs
index 2db1688..8e21915 100644
--- a/src/Lucene.Net.Tests.Spatial/Prefix/TestRecursivePrefixTreeStrategy.cs
+++ b/src/Lucene.Net.Tests.Spatial/Prefix/TestRecursivePrefixTreeStrategy.cs
@@ -1,9 +1,9 @@
 using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Spatial.Queries;
 using NUnit.Framework;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Distance;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Distance;
+using Spatial4n.Shapes;
 using System.Collections.Generic;
 using System.Globalization;
 using JCG = J2N.Collections.Generic;
@@ -35,7 +35,7 @@ namespace Lucene.Net.Spatial.Prefix
         private void init(int maxLength)
         {
             this.maxLength = maxLength;
-            this.ctx = SpatialContext.GEO;
+            this.ctx = SpatialContext.Geo;
             GeohashPrefixTree grid = new GeohashPrefixTree(ctx, maxLength);
             this.strategy = new RecursivePrefixTreeStrategy(grid, GetType().Name);
         }
@@ -60,7 +60,7 @@ namespace Lucene.Net.Spatial.Prefix
             init(GeohashPrefixTree.MaxLevelsPossible);
             GeohashPrefixTree grid = (GeohashPrefixTree)((RecursivePrefixTreeStrategy)strategy).Grid;
             //DWS: I know this to be true.  11 is needed for one meter
-            double degrees = DistanceUtils.Dist2Degrees(0.001, DistanceUtils.EARTH_MEAN_RADIUS_KM);
+            double degrees = DistanceUtils.Dist2Degrees(0.001, DistanceUtils.EarthMeanRadiusKilometers);
             assertEquals(11, grid.GetLevelForDistance(degrees));
         }
 
@@ -69,17 +69,17 @@ namespace Lucene.Net.Spatial.Prefix
         {
             init(GeohashPrefixTree.MaxLevelsPossible);
 
-            Spatial4n.Core.Shapes.IPoint iPt = ctx.MakePoint(2.8028712999999925, 48.3708044);//lon, lat
+            IPoint iPt = ctx.MakePoint(2.8028712999999925, 48.3708044);//lon, lat
             AddDocument(newDoc("iPt", iPt));
             Commit();
 
-            Spatial4n.Core.Shapes.IPoint qPt = ctx.MakePoint(2.4632387000000335, 48.6003516);
+            IPoint qPt = ctx.MakePoint(2.4632387000000335, 48.6003516);
 
-            double KM2DEG = DistanceUtils.Dist2Degrees(1, DistanceUtils.EARTH_MEAN_RADIUS_KM);
+            double KM2DEG = DistanceUtils.Dist2Degrees(1, DistanceUtils.EarthMeanRadiusKilometers);
             double DEG2KM = 1 / KM2DEG;
 
             double DIST = 35.75;//35.7499...
-            assertEquals(DIST, ctx.DistCalc.Distance(iPt, qPt) * DEG2KM, 0.001);
+            assertEquals(DIST, ctx.DistanceCalculator.Distance(iPt, qPt) * DEG2KM, 0.001);
 
             //distErrPct will affect the query shape precision. The indexed precision
             // was set to nearly zilch via init(GeohashPrefixTree.getMaxLevelsPossible());
@@ -99,7 +99,7 @@ namespace Lucene.Net.Spatial.Prefix
             checkHits(q(qPt, 34 * KM2DEG, distErrPct), 0, null);
         }
 
-        private SpatialArgs q(Spatial4n.Core.Shapes.IPoint pt, double distDEG, double distErrPct)
+        private SpatialArgs q(IPoint pt, double distDEG, double distErrPct)
         {
             IShape shape = ctx.MakeCircle(pt, distDEG);
             SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, shape);
diff --git a/src/Lucene.Net.Tests.Spatial/Prefix/TestTermQueryPrefixGridStrategy.cs b/src/Lucene.Net.Tests.Spatial/Prefix/TestTermQueryPrefixGridStrategy.cs
index 38ef37e..b6a74f4 100644
--- a/src/Lucene.Net.Tests.Spatial/Prefix/TestTermQueryPrefixGridStrategy.cs
+++ b/src/Lucene.Net.Tests.Spatial/Prefix/TestTermQueryPrefixGridStrategy.cs
@@ -3,7 +3,8 @@ using Lucene.Net.Index;
 using Lucene.Net.Spatial.Prefix.Tree;
 using Lucene.Net.Spatial.Queries;
 using NUnit.Framework;
-using Spatial4n.Core.Context;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 
 namespace Lucene.Net.Spatial.Prefix
 {
@@ -29,10 +30,10 @@ namespace Lucene.Net.Spatial.Prefix
         [Test]
         public virtual void TestNGramPrefixGridLosAngeles()
         {
-            SpatialContext ctx = SpatialContext.GEO;
+            SpatialContext ctx = SpatialContext.Geo;
             TermQueryPrefixTreeStrategy prefixGridStrategy = new TermQueryPrefixTreeStrategy(new QuadPrefixTree(ctx), "geo");
-
-            Spatial4n.Core.Shapes.IShape point = ctx.MakePoint(-118.243680, 34.052230);
+            
+            IShape point = ctx.MakePoint(-118.243680, 34.052230);
 
             Document losAngeles = new Document();
             losAngeles.Add(new StringField("name", "Los Angeles", Field.Store.YES));
@@ -40,7 +41,7 @@ namespace Lucene.Net.Spatial.Prefix
             {
                 losAngeles.Add(field);
             }
-            losAngeles.Add(new StoredField(prefixGridStrategy.FieldName, point.toString()));//just for diagnostics
+            losAngeles.Add(new StoredField(prefixGridStrategy.FieldName, point.ToString()));//just for diagnostics
 
             addDocumentsAndCommit(new Document[] { losAngeles });
 
diff --git a/src/Lucene.Net.Tests.Spatial/Prefix/Tree/SpatialPrefixTreeTest.cs b/src/Lucene.Net.Tests.Spatial/Prefix/Tree/SpatialPrefixTreeTest.cs
index 0d31ac7..870d3a2 100644
--- a/src/Lucene.Net.Tests.Spatial/Prefix/Tree/SpatialPrefixTreeTest.cs
+++ b/src/Lucene.Net.Tests.Spatial/Prefix/Tree/SpatialPrefixTreeTest.cs
@@ -2,8 +2,8 @@
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Queries;
 using NUnit.Framework;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 using Console = Lucene.Net.Util.SystemConsole;
 
@@ -35,7 +35,7 @@ namespace Lucene.Net.Spatial.Prefix.Tree
         public override void SetUp()
         {
             base.SetUp();
-            ctx = SpatialContext.GEO;
+            ctx = SpatialContext.Geo;
         }
 
         [Test]
diff --git a/src/Lucene.Net.Tests.Spatial/Query/SpatialArgsParserTest.cs b/src/Lucene.Net.Tests.Spatial/Query/SpatialArgsParserTest.cs
index 478fffb..0088970 100644
--- a/src/Lucene.Net.Tests.Spatial/Query/SpatialArgsParserTest.cs
+++ b/src/Lucene.Net.Tests.Spatial/Query/SpatialArgsParserTest.cs
@@ -1,7 +1,7 @@
 using Lucene.Net.Util;
 using NUnit.Framework;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 
 namespace Lucene.Net.Spatial.Queries
@@ -25,7 +25,7 @@ namespace Lucene.Net.Spatial.Queries
 
     public class SpatialArgsParserTest : LuceneTestCase
     {
-        private SpatialContext ctx = SpatialContext.GEO;
+        private SpatialContext ctx = SpatialContext.Geo;
 
         //The args parser is only dependent on the ctx for IO so I don't care to test
         // with other implementations.
diff --git a/src/Lucene.Net.Tests.Spatial/QueryEqualsHashCodeTest.cs b/src/Lucene.Net.Tests.Spatial/QueryEqualsHashCodeTest.cs
index 9d23290..ee3d53b 100644
--- a/src/Lucene.Net.Tests.Spatial/QueryEqualsHashCodeTest.cs
+++ b/src/Lucene.Net.Tests.Spatial/QueryEqualsHashCodeTest.cs
@@ -5,8 +5,8 @@ using Lucene.Net.Spatial.Serialized;
 using Lucene.Net.Spatial.Vector;
 using Lucene.Net.Util;
 using NUnit.Framework;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
 using JCG = J2N.Collections.Generic;
@@ -32,7 +32,7 @@ namespace Lucene.Net.Spatial
 
     public class QueryEqualsHashCodeTest : LuceneTestCase
     {
-        private readonly SpatialContext ctx = SpatialContext.GEO;
+        private readonly SpatialContext ctx = SpatialContext.Geo;
 
         [Test]
         public virtual void TestEqualsHashCode()
diff --git a/src/Lucene.Net.Tests.Spatial/Serialized/SerializedStrategyTest.cs b/src/Lucene.Net.Tests.Spatial/Serialized/SerializedStrategyTest.cs
index b4d00a6..fe90896 100644
--- a/src/Lucene.Net.Tests.Spatial/Serialized/SerializedStrategyTest.cs
+++ b/src/Lucene.Net.Tests.Spatial/Serialized/SerializedStrategyTest.cs
@@ -1,6 +1,6 @@
 using Lucene.Net.Search;
 using NUnit.Framework;
-using Spatial4n.Core.Context;
+using Spatial4n.Context;
 
 namespace Lucene.Net.Spatial.Serialized
 {
@@ -26,7 +26,7 @@ namespace Lucene.Net.Spatial.Serialized
         public override void SetUp()
         {
             base.SetUp();
-            this.ctx = SpatialContext.GEO;
+            this.ctx = SpatialContext.Geo;
             this.strategy = new SerializedDVStrategy(ctx, "serialized");
         }
 
diff --git a/src/Lucene.Net.Tests.Spatial/SpatialArgsTest.cs b/src/Lucene.Net.Tests.Spatial/SpatialArgsTest.cs
index 8dd70f4..9ba5c5e 100644
--- a/src/Lucene.Net.Tests.Spatial/SpatialArgsTest.cs
+++ b/src/Lucene.Net.Tests.Spatial/SpatialArgsTest.cs
@@ -1,8 +1,8 @@
 using Lucene.Net.Spatial.Queries;
 using Lucene.Net.Util;
 using NUnit.Framework;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 
 namespace Lucene.Net.Spatial
@@ -29,7 +29,7 @@ namespace Lucene.Net.Spatial
         [Test]
         public void CalcDistanceFromErrPct()
         {
-            SpatialContext ctx = SpatialContext.GEO;
+            SpatialContext ctx = SpatialContext.Geo;
             double DEP = 0.5;//distErrPct
 
             //the result is the diagonal distance from the center to the closest corner,
diff --git a/src/Lucene.Net.Tests.Spatial/SpatialExample.cs b/src/Lucene.Net.Tests.Spatial/SpatialExample.cs
index c6f7c45..7df28c3 100644
--- a/src/Lucene.Net.Tests.Spatial/SpatialExample.cs
+++ b/src/Lucene.Net.Tests.Spatial/SpatialExample.cs
@@ -8,9 +8,9 @@ using Lucene.Net.Spatial.Queries;
 using Lucene.Net.Store;
 using Lucene.Net.Util;
 using NUnit.Framework;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Distance;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Distance;
+using Spatial4n.Shapes;
 using System;
 using System.Globalization;
 
@@ -79,7 +79,7 @@ namespace Lucene.Net.Spatial
         {
             //Typical geospatial context
             //  These can also be constructed from SpatialContextFactory
-            this.ctx = SpatialContext.GEO;
+            this.ctx = SpatialContext.Geo;
 
             int maxLevels = 11;//results in sub-meter precision for geohash
                                //TODO demo lookup by detail distance
@@ -125,7 +125,7 @@ namespace Lucene.Net.Spatial
                 //store it too; the format is up to you
                 //  (assume point in this example)
                 IPoint pt = (IPoint)shape;
-                doc.Add(new StoredField(strategy.FieldName, pt.X.ToString(CultureInfo.InvariantCulture) + " " + pt.Y.ToString(CultureInfo.InvariantCulture)));
+                doc.Add(new StoredField(strategy.FieldName, J2N.Numerics.Double.ToString(pt.X, CultureInfo.InvariantCulture) + " " + J2N.Numerics.Double.ToString(pt.Y, CultureInfo.InvariantCulture)));
             }
 
             return doc;
@@ -142,7 +142,7 @@ namespace Lucene.Net.Spatial
                 //Search with circle
                 //note: SpatialArgs can be parsed from a string
                 SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects,
-                    ctx.MakeCircle(-80.0, 33.0, DistanceUtils.Dist2Degrees(200, DistanceUtils.EARTH_MEAN_RADIUS_KM)));
+                    ctx.MakeCircle(-80.0, 33.0, DistanceUtils.Dist2Degrees(200, DistanceUtils.EarthMeanRadiusKilometers)));
                 Filter filter = strategy.MakeFilter(args);
                 TopDocs docs = indexSearcher.Search(new MatchAllDocsQuery(), filter, 10, idSort);
                 AssertDocMatchedIds(indexSearcher, docs, 2);
@@ -155,14 +155,14 @@ namespace Lucene.Net.Spatial
                 double x = double.Parse(doc1Str.Substring(0, spaceIdx - 0), CultureInfo.InvariantCulture);
                 double y = double.Parse(doc1Str.Substring(spaceIdx + 1), CultureInfo.InvariantCulture);
                 double doc1DistDEG = ctx.CalcDistance(args.Shape.Center, x, y);
-                assertEquals(121.6d, DistanceUtils.Degrees2Dist(doc1DistDEG, DistanceUtils.EARTH_MEAN_RADIUS_KM), 0.1);
+                assertEquals(121.6d, DistanceUtils.Degrees2Dist(doc1DistDEG, DistanceUtils.EarthMeanRadiusKilometers), 0.1);
                 //or more simply:
-                assertEquals(121.6d, doc1DistDEG * DistanceUtils.DEG_TO_KM, 0.1);
+                assertEquals(121.6d, doc1DistDEG * DistanceUtils.DegreesToKilometers, 0.1);
             }
             //--Match all, order by distance ascending
             {
                 IPoint pt = ctx.MakePoint(60, -50);
-                ValueSource valueSource = strategy.MakeDistanceValueSource(pt, DistanceUtils.DEG_TO_KM);//the distance (in km)
+                ValueSource valueSource = strategy.MakeDistanceValueSource(pt, DistanceUtils.DegreesToKilometers);//the distance (in km)
                 Sort distSort = new Sort(valueSource.GetSortField(false)).Rewrite(indexSearcher);//false=asc dist
                 TopDocs docs = indexSearcher.Search(new MatchAllDocsQuery(), 10, distSort);
                 AssertDocMatchedIds(indexSearcher, docs, 4, 20, 2);
diff --git a/src/Lucene.Net.Tests.Spatial/SpatialTestCase.cs b/src/Lucene.Net.Tests.Spatial/SpatialTestCase.cs
index 383e6d5..5145d45 100644
--- a/src/Lucene.Net.Tests.Spatial/SpatialTestCase.cs
+++ b/src/Lucene.Net.Tests.Spatial/SpatialTestCase.cs
@@ -6,8 +6,8 @@ using Lucene.Net.Index;
 using Lucene.Net.Index.Extensions;
 using Lucene.Net.Search;
 using Lucene.Net.Util;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
 using System.Text;
@@ -128,7 +128,7 @@ namespace Lucene.Net.Spatial
             }
         }
 
-        protected virtual Spatial4n.Core.Shapes.IPoint randomPoint()
+        protected virtual IPoint randomPoint()
         {
             IRectangle WB = ctx.WorldBounds;
             return ctx.MakePoint(
diff --git a/src/Lucene.Net.Tests.Spatial/SpatialTestData.cs b/src/Lucene.Net.Tests.Spatial/SpatialTestData.cs
index 671ac72..96551b6 100644
--- a/src/Lucene.Net.Tests.Spatial/SpatialTestData.cs
+++ b/src/Lucene.Net.Tests.Spatial/SpatialTestData.cs
@@ -1,6 +1,6 @@
 using J2N.Text;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -62,7 +62,7 @@ namespace Lucene.Net.Spatial
                     {
                         data.shape = ctx.ReadShapeFromWkt(vals[2]);
                     }
-                    catch (Spatial4n.Core.Exceptions.ParseException e) // LUCENENET: Spatial4n has its own ParseException that is different than the one in Support
+                    catch (Spatial4n.Exceptions.ParseException e) // LUCENENET: Spatial4n has its own ParseException that is different than the one in Support
                     {
                         throw RuntimeException.Create(e);
                     }
diff --git a/src/Lucene.Net.Tests.Spatial/SpatialTestQuery.cs b/src/Lucene.Net.Tests.Spatial/SpatialTestQuery.cs
index 8a33fba..fc29fad 100644
--- a/src/Lucene.Net.Tests.Spatial/SpatialTestQuery.cs
+++ b/src/Lucene.Net.Tests.Spatial/SpatialTestQuery.cs
@@ -1,6 +1,6 @@
 using J2N.Text;
 using Lucene.Net.Spatial.Queries;
-using Spatial4n.Core.Context;
+using Spatial4n.Context;
 using System;
 using System.Collections.Generic;
 using System.IO;
diff --git a/src/Lucene.Net.Tests.Spatial/StrategyTestCase.cs b/src/Lucene.Net.Tests.Spatial/StrategyTestCase.cs
index a0eebfd..6d3152d 100644
--- a/src/Lucene.Net.Tests.Spatial/StrategyTestCase.cs
+++ b/src/Lucene.Net.Tests.Spatial/StrategyTestCase.cs
@@ -4,8 +4,8 @@ using Lucene.Net.Queries.Function;
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Queries;
 using Lucene.Net.Util;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
 using System.IO;
diff --git a/src/Lucene.Net.Tests.Spatial/TestTestFramework.cs b/src/Lucene.Net.Tests.Spatial/TestTestFramework.cs
index 9c4c290..c8abb3d 100644
--- a/src/Lucene.Net.Tests.Spatial/TestTestFramework.cs
+++ b/src/Lucene.Net.Tests.Spatial/TestTestFramework.cs
@@ -1,8 +1,8 @@
 using Lucene.Net.Spatial.Queries;
 using Lucene.Net.Util;
 using NUnit.Framework;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -35,7 +35,7 @@ namespace Lucene.Net.Spatial
             String name = StrategyTestCase.RESOURCE_PATH + StrategyTestCase.QTEST_Cities_Intersects_BBox;
 
             Stream @in = GetType().getResourceAsStream(name);
-            SpatialContext ctx = SpatialContext.GEO;
+            SpatialContext ctx = SpatialContext.Geo;
             IEnumerator<SpatialTestQuery> iter = SpatialTestQuery.GetTestQueries(
                 new SpatialArgsParser(), ctx, name, @in);//closes the InputStream
             IList<SpatialTestQuery> tests = new JCG.List<SpatialTestQuery>();
diff --git a/src/Lucene.Net.Tests.Spatial/Vector/TestPointVectorStrategy.cs b/src/Lucene.Net.Tests.Spatial/Vector/TestPointVectorStrategy.cs
index a43faf0..53a78a8 100644
--- a/src/Lucene.Net.Tests.Spatial/Vector/TestPointVectorStrategy.cs
+++ b/src/Lucene.Net.Tests.Spatial/Vector/TestPointVectorStrategy.cs
@@ -1,8 +1,8 @@
 using Lucene.Net.Search;
 using Lucene.Net.Spatial.Queries;
 using NUnit.Framework;
-using Spatial4n.Core.Context;
-using Spatial4n.Core.Shapes;
+using Spatial4n.Context;
+using Spatial4n.Shapes;
 using System;
 
 namespace Lucene.Net.Spatial.Vector
@@ -29,7 +29,7 @@ namespace Lucene.Net.Spatial.Vector
         public override void SetUp()
         {
             base.SetUp();
-            this.ctx = SpatialContext.GEO;
+            this.ctx = SpatialContext.Geo;
             this.strategy = new PointVectorStrategy(ctx, GetType().Name);
         }