diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6718088..0ec4163 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,10 +5,11 @@ on: [push, pull_request]
jobs:
test:
name: ${{ matrix.DB }}
- runs-on: ubuntu-latest
+ runs-on: ${{ matrix.OS }}
strategy:
fail-fast: false
matrix:
+ os: [ubuntu-latest]
include:
- DB: MsSql2012 (SQL Server 2017)
DB_INIT: |
@@ -55,6 +56,15 @@ jobs:
DB_INIT: docker run -d -e POSTGRES_PASSWORD=nhsp_test -p 15433:5432 -v ./Tests.NHibernate.Spatial.PostGis30/initdb:/docker-entrypoint-initdb.d postgis/postgis:16-3.4
TEST_PROJECT: Tests.NHibernate.Spatial.PostGis30
+ - DB: SpatiaLite (SpatiaLite 4)
+ DB_INIT: sudo apt install libsqlite3-mod-spatialite=4.*
+ TEST_PROJECT: Tests.NHibernate.Spatial.SpatiaLite
+ OS: ubuntu-20.04
+
+ - DB: SpatiaLite (SpatiaLite 5)
+ DB_INIT: sudo apt install libsqlite3-mod-spatialite=5.*
+ TEST_PROJECT: Tests.NHibernate.Spatial.SpatiaLite
+
steps:
- name: Checkout repository
uses: actions/checkout@v4
diff --git a/BuildPackages.bat b/BuildPackages.bat
index c6d6087..691dafd 100644
--- a/BuildPackages.bat
+++ b/BuildPackages.bat
@@ -11,4 +11,5 @@ dotnet pack %options% NHibernate.Spatial
dotnet pack %options% NHibernate.Spatial.MsSql
dotnet pack %options% NHibernate.Spatial.MySQL
REM dotnet pack %options% NHibernate.Spatial.Oracle
+dotnet pack %options% NHibernate.Spatial.SpatiaLite
dotnet pack %options% NHibernate.Spatial.PostGis
diff --git a/NHibernate.Spatial.SpatiaLite/Dialect/SpatiaLiteDialect.cs b/NHibernate.Spatial.SpatiaLite/Dialect/SpatiaLiteDialect.cs
new file mode 100644
index 0000000..e019fdc
--- /dev/null
+++ b/NHibernate.Spatial.SpatiaLite/Dialect/SpatiaLiteDialect.cs
@@ -0,0 +1,542 @@
+using System;
+using System.Globalization;
+using System.Text;
+using NHibernate.Dialect;
+using NHibernate.Spatial.Dialect.Function;
+using NHibernate.Spatial.Metadata;
+using NHibernate.Spatial.Type;
+using NHibernate.SqlCommand;
+using NHibernate.Type;
+
+namespace NHibernate.Spatial.Dialect
+{
+ public class SpatiaLiteDialect : SQLiteDialect, ISpatialDialect
+ {
+ private static readonly IType GeometryTypeInstance = new CustomType(typeof(SpatiaLiteGeometryType), null);
+
+ public SpatiaLiteDialect()
+ {
+ SpatialDialect.LastInstantiated = this;
+
+ RegisterBasicFunctions();
+ RegisterFunctions();
+ }
+
+ public IType GeometryType => GeometryTypeInstance;
+
+ public string MultipleQueriesSeparator => ";";
+
+ public IGeometryUserType CreateGeometryUserType()
+ {
+ return new SpatiaLiteGeometryType();
+ }
+
+ public SqlString GetSpatialTransformString(object geometry, int srid)
+ {
+ return new SqlStringBuilder()
+ .Add(SpatialDialect.IsoPrefix)
+ .Add("Transform(")
+ .AddObject(geometry)
+ .Add(",")
+ .Add(srid.ToString(NumberFormatInfo.InvariantInfo))
+ .Add(")")
+ .ToSqlString();
+ }
+
+ public SqlString GetSpatialAggregateString(object geometry, SpatialAggregate aggregate)
+ {
+ string aggregateFunction;
+ switch (aggregate)
+ {
+ case SpatialAggregate.Collect:
+ aggregateFunction = "Collect";
+ break;
+ case SpatialAggregate.ConvexHull:
+ return new SqlStringBuilder()
+ .Add(SpatialDialect.IsoPrefix)
+ .Add("Collect(")
+ .Add(SpatialDialect.IsoPrefix)
+ .Add(aggregate.ToString())
+ .Add("(")
+ .AddObject(geometry)
+ .Add("))")
+ .ToSqlString();
+ case SpatialAggregate.Envelope:
+ aggregateFunction = "Extent";
+ break;
+ case SpatialAggregate.Intersection:
+ throw new NotSupportedException("SpatialAggregate Intersection"); // TODO Check whether collect works here too
+ case SpatialAggregate.Union:
+ aggregateFunction = "GUnion";
+ break;
+ default:
+ throw new ArgumentException("Invalid spatial aggregate argument");
+ }
+ return new SqlStringBuilder()
+ .Add(aggregateFunction)
+ .Add("(")
+ .AddObject(geometry)
+ .Add(")")
+ .ToSqlString();
+ }
+
+ public SqlString GetSpatialAnalysisString(object geometry,
+ SpatialAnalysis analysis,
+ object extraArgument)
+ {
+ switch (analysis)
+ {
+ case SpatialAnalysis.Buffer:
+ if (!(extraArgument is Parameter || new SqlString(Parameter.Placeholder).Equals(extraArgument)))
+ {
+ extraArgument = Convert.ToString(extraArgument, NumberFormatInfo.InvariantInfo);
+ }
+
+ return new SqlStringBuilder(6)
+ .Add(SpatialDialect.IsoPrefix)
+ .Add("Buffer(")
+ .AddObject(geometry)
+ .Add(", ")
+ .AddObject(extraArgument)
+ .Add(")")
+ .ToSqlString();
+ case SpatialAnalysis.ConvexHull:
+ return new SqlStringBuilder()
+ .Add(SpatialDialect.IsoPrefix)
+ .Add("ConvexHull(")
+ .AddObject(geometry)
+ .Add(")")
+ .ToSqlString();
+ case SpatialAnalysis.Difference:
+ case SpatialAnalysis.Distance:
+ case SpatialAnalysis.Intersection:
+ case SpatialAnalysis.SymDifference:
+ case SpatialAnalysis.Union:
+ return new SqlStringBuilder()
+ .Add(SpatialDialect.IsoPrefix)
+ .Add(analysis.ToString())
+ .Add("(")
+ .AddObject(geometry)
+ .Add(",")
+ .AddObject(extraArgument)
+ .Add(")")
+ .ToSqlString();
+ default:
+ throw new ArgumentException("Invalid spatial analysis argument");
+ }
+ }
+
+ public SqlString GetSpatialValidationString(object geometry,
+ SpatialValidation validation,
+ bool criterion)
+ {
+ return new SqlStringBuilder()
+ .Add(SpatialDialect.IsoPrefix)
+ .Add(validation.ToString())
+ .Add("(")
+ .AddObject(geometry)
+ .Add(")")
+ .Add(" = 1")
+ .ToSqlString();
+ }
+
+ public SqlString GetSpatialRelateString(object geometry,
+ object anotherGeometry,
+ object pattern,
+ bool isStringPattern,
+ bool criterion)
+ {
+ var relateString = new SqlStringBuilder()
+ .Add(SpatialDialect.IsoPrefix)
+ .Add("Relate(")
+ .AddObject(geometry)
+ .Add(",")
+ .AddObject(anotherGeometry)
+ .Add(",");
+
+ if (isStringPattern)
+ {
+ relateString.Add("'");
+ }
+
+ relateString.Add(pattern.ToString());
+
+ if (isStringPattern)
+ {
+ relateString.Add("'");
+ }
+
+ relateString.Add(")");
+
+ return relateString.ToSqlString();
+ }
+
+ public SqlString GetSpatialRelationString(object geometry,
+ SpatialRelation relation,
+ object anotherGeometry,
+ bool criterion)
+ {
+ switch (relation)
+ {
+ case SpatialRelation.Covers:
+ string[] patterns =
+ {
+ "T*****FF*",
+ "*T****FF*",
+ "***T**FF*",
+ "****T*FF*"
+ };
+ var builder = new SqlStringBuilder();
+ builder.Add("(");
+ for (int i = 0; i < patterns.Length; i++)
+ {
+ if (i > 0)
+ {
+ builder.Add(" OR ");
+ }
+ builder
+ .Add(SpatialDialect.IsoPrefix)
+ .Add("Relate")
+ .Add("(")
+ .AddObject(geometry)
+ .Add(", ")
+ .AddObject(anotherGeometry)
+ .Add(", '")
+ .Add(patterns[i])
+ .Add("')")
+ .ToSqlString();
+ }
+ builder.Add(")");
+ return builder.ToSqlString();
+ case SpatialRelation.CoveredBy:
+ return GetSpatialRelationString(anotherGeometry, SpatialRelation.Covers, geometry, criterion);
+ case SpatialRelation.EqualsExact:
+ throw new ArgumentOutOfRangeException(nameof(relation), relation, "Unsupported spatial relation");
+ default:
+ return new SqlStringBuilder(6)
+ .Add(SpatialDialect.IsoPrefix)
+ .Add(relation.ToString())
+ .Add("(")
+ .AddObject(geometry)
+ .Add(", ")
+ .AddObject(anotherGeometry)
+ .Add(")")
+ .ToSqlString();
+ }
+ }
+
+ public SqlString GetSpatialRelationString(object geometry, SpatialRelation relation, object anotherGeometry, object parameter, bool criterion)
+ {
+ switch (relation)
+ {
+ case SpatialRelation.IsWithinDistance:
+ return new SqlStringBuilder()
+ .Add("PtDistWithin")
+ .Add("(")
+ .AddObject(geometry)
+ .Add(", ")
+ .AddObject(anotherGeometry)
+ .Add(", ")
+ .Add(parameter.ToString())
+ .Add(")")
+ .ToSqlString();
+ case SpatialRelation.Relate:
+ return new SqlStringBuilder()
+ .Add(SpatialDialect.IsoPrefix)
+ .Add(relation.ToString())
+ .Add("(")
+ .AddObject(geometry)
+ .Add(", ")
+ .AddObject(anotherGeometry)
+ .Add(", '")
+ .Add(parameter.ToString())
+ .Add("')")
+ .ToSqlString();
+ default:
+ throw new ArgumentOutOfRangeException(nameof(relation), relation, "Unsupported spatial relation");
+ }
+ }
+
+ public SqlString GetSpatialFilterString(string tableAlias,
+ string geometryColumnName,
+ string primaryKeyColumnName,
+ string tableName,
+ Parameter parameter)
+ {
+ return new SqlStringBuilder(30)
+ .Add("(MbrIntersects(")
+ .Add(tableAlias)
+ .Add(".")
+ .Add(geometryColumnName)
+ .Add(", ")
+ .Add(parameter)
+ .Add("))")
+ .ToSqlString();
+ }
+
+ public SqlString GetSpatialFilterString(string tableAlias,
+ string geometryColumnName,
+ string primaryKeyColumnName,
+ string tableName)
+ {
+ return new SqlStringBuilder(30)
+ .Add("(MbrIntersects(")
+ .Add(tableAlias)
+ .Add(".")
+ .Add(geometryColumnName)
+ .Add(", ")
+ .AddParameter()
+ .Add("))")
+ .ToSqlString();
+ }
+
+ ///
+ /// Gets the spatial create string.
+ ///
+ /// The schema.
+ ///
+ public string GetSpatialCreateString(string schema)
+ {
+ return string.Empty;
+ }
+
+ ///
+ /// Gets the spatial create string.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public string GetSpatialCreateString(string schema,
+ string table,
+ string column,
+ int srid,
+ string subtype,
+ int dimension,
+ bool isNullable)
+ {
+ var builder = new StringBuilder();
+
+ builder.AppendFormat("ALTER TABLE {0} DROP COLUMN {1};"
+ , QuoteForTableName(table)
+ , QuoteForColumnName(column)
+ );
+
+ builder.AppendFormat("SELECT AddGeometryColumn('{0}','{1}',{2},'{3}','{4}', {5});",
+ table, column, srid, subtype, ToXyzm(dimension), isNullable ? 0 : 1);
+
+ builder.AppendFormat("SELECT CreateSpatialIndex('{0}','{1}');", table, column);
+
+ return builder.ToString();
+ }
+
+ ///
+ /// Gets the spatial drop string.
+ ///
+ /// The schema.
+ ///
+ public string GetSpatialDropString(string schema)
+ {
+ return string.Empty;
+ }
+
+ ///
+ /// Gets the spatial drop string.
+ ///
+ /// The schema.
+ /// The table.
+ /// The column.
+ ///
+ public string GetSpatialDropString(string schema, string table, string column)
+ {
+ var builder = new StringBuilder();
+
+ builder.AppendFormat("SELECT DisableSpatialIndex('{0}','{1}');",
+ table, column);
+
+ builder.AppendFormat("SELECT DiscardGeometryColumn('{0}','{1}');",
+ table, column);
+
+ builder.AppendFormat("DELETE FROM geometry_columns where f_table_name = '{0}' AND f_geometry_column = '{1}';",
+ table, column);
+
+ return builder.ToString();
+ }
+
+ public bool SupportsSpatialMetadata(MetadataClass metadataClass)
+ {
+ return true;
+ }
+
+ private static string ToXyzm(int dimension)
+ {
+ switch (dimension)
+ {
+ case 3:
+ return "XYZ";
+ case 4:
+ return "XYZM";
+ }
+ return "XY";
+ }
+
+ private static int ToGeometryType(string subtype)
+ {
+ switch (subtype)
+ {
+ case "POINT":
+ return 1;
+ case "LINESTRING":
+ return 2;
+ case "POLYGON":
+ return 3;
+ case "MULTIPOINT":
+ return 4;
+ case "MULTILINESTRING":
+ return 5;
+ case "MULTIPOLYGON":
+ return 6;
+ case "GEOMETRYCOLLECTION":
+ return 7;
+ default:
+ throw new Exception("Should never reach here");
+ }
+ }
+
+ #region Functions registration
+
+ private void RegisterBasicFunctions()
+ {
+ // Relations
+ RegisterSpatialFunction(SpatialRelation.Contains);
+ RegisterSpatialFunction(SpatialRelation.CoveredBy);
+ RegisterSpatialFunction(SpatialRelation.Covers);
+ RegisterSpatialFunction(SpatialRelation.Crosses);
+ RegisterSpatialFunction(SpatialRelation.Disjoint);
+ RegisterSpatialFunction(SpatialRelation.Equals);
+ RegisterSpatialFunction(SpatialRelation.Intersects);
+ RegisterSpatialFunction(SpatialRelation.Overlaps);
+ RegisterSpatialFunction(SpatialRelation.Touches);
+ RegisterSpatialFunction(SpatialRelation.Within);
+
+ // Analysis
+ RegisterSpatialFunction(SpatialAnalysis.Buffer);
+ RegisterSpatialFunction(SpatialAnalysis.ConvexHull);
+ RegisterSpatialFunction(SpatialAnalysis.Difference);
+ RegisterSpatialFunction(SpatialAnalysis.Distance);
+ RegisterSpatialFunction(SpatialAnalysis.Intersection);
+ RegisterSpatialFunction(SpatialAnalysis.SymDifference);
+ RegisterSpatialFunction(SpatialAnalysis.Union);
+
+ // Validations
+ RegisterSpatialFunction(SpatialValidation.IsClosed);
+ RegisterSpatialFunction(SpatialValidation.IsEmpty);
+ RegisterSpatialFunction(SpatialValidation.IsRing);
+ RegisterSpatialFunction(SpatialValidation.IsSimple);
+ RegisterSpatialFunction(SpatialValidation.IsValid);
+ }
+
+ protected override void RegisterFunctions()
+ {
+ RegisterSpatialFunction("Boundary");
+ RegisterSpatialFunction("Centroid");
+ RegisterSpatialFunction("EndPoint");
+ RegisterSpatialFunction("Envelope");
+ RegisterSpatialFunction("ExteriorRing");
+ RegisterSpatialFunction("GeometryN", 2);
+ RegisterSpatialFunction("InteriorRingN", 2);
+ RegisterSpatialFunction("PointN", 2);
+ RegisterSpatialFunction("PointOnSurface");
+ RegisterSpatialFunction("Simplify", 2);
+ RegisterSpatialFunction("StartPoint");
+ RegisterSpatialFunction("Transform", 2);
+
+ RegisterSpatialFunction("GeomCollFromText", 2);
+ RegisterSpatialFunction("GeomCollFromWKB", 2);
+ RegisterSpatialFunction("GeomFromText", 2);
+ RegisterSpatialFunction("GeomFromWKB", 2);
+ RegisterSpatialFunction("LineFromText", 2);
+ RegisterSpatialFunction("LineFromWKB", 2);
+ RegisterSpatialFunction("PointFromText", 2);
+ RegisterSpatialFunction("PointFromWKB", 2);
+ RegisterSpatialFunction("PolyFromText", 2);
+ RegisterSpatialFunction("PolyFromWKB", 2);
+ RegisterSpatialFunction("MLineFromText", 2);
+ RegisterSpatialFunction("MLineFromWKB", 2);
+ RegisterSpatialFunction("MPointFromText", 2);
+ RegisterSpatialFunction("MPointFromWKB", 2);
+ RegisterSpatialFunction("MPolyFromText", 2);
+ RegisterSpatialFunction("MPolyFromWKB", 2);
+
+ RegisterSpatialFunction("AsBinary", NHibernateUtil.Binary);
+
+ RegisterSpatialFunction("AsText", NHibernateUtil.String);
+ RegisterSpatialFunction("AsGML", NHibernateUtil.String);
+ RegisterSpatialFunction("GeometryType", NHibernateUtil.String);
+
+ RegisterSpatialFunction("Area", NHibernateUtil.Double);
+ RegisterSpatialFunction("Length", "GLength", NHibernateUtil.Double);
+ RegisterSpatialFunction("X", NHibernateUtil.Double);
+ RegisterSpatialFunction("Y", NHibernateUtil.Double);
+
+ RegisterSpatialFunction("SRID", NHibernateUtil.Int32);
+ RegisterSpatialFunction("Dimension", NHibernateUtil.Int32);
+ RegisterSpatialFunction("NumGeometries", NHibernateUtil.Int32);
+ RegisterSpatialFunction("NumInteriorRings", "NumInteriorRing", NHibernateUtil.Int32);
+ RegisterSpatialFunction("NumPoints", NHibernateUtil.Int32);
+
+ RegisterSpatialFunction("Relate", NHibernateUtil.Boolean, 3);
+ }
+
+ private void RegisterSpatialFunction(string standardName, string dialectName, IType returnedType, int allowedArgsCount)
+ {
+ RegisterFunction(SpatialDialect.HqlPrefix + standardName, new SpatialStandardSafeFunction(dialectName, returnedType, allowedArgsCount));
+ }
+
+ private void RegisterSpatialFunction(string standardName, string dialectName, IType returnedType)
+ {
+ RegisterSpatialFunction(standardName, dialectName, returnedType, 1);
+ }
+
+ private void RegisterSpatialFunction(string name, IType returnedType, int allowedArgsCount)
+ {
+ RegisterSpatialFunction(name, name, returnedType, allowedArgsCount);
+ }
+
+ private void RegisterSpatialFunction(string name, IType returnedType)
+ {
+ RegisterSpatialFunction(name, name, returnedType);
+ }
+
+ private void RegisterSpatialFunction(string name, int allowedArgsCount)
+ {
+ RegisterSpatialFunction(name, GeometryType, allowedArgsCount);
+ }
+
+ private void RegisterSpatialFunction(string name)
+ {
+ RegisterSpatialFunction(name, GeometryType);
+ }
+
+ private void RegisterSpatialFunction(SpatialRelation relation)
+ {
+ RegisterFunction(SpatialDialect.HqlPrefix + relation, new SpatialRelationFunction(this, relation));
+ }
+
+ private void RegisterSpatialFunction(SpatialValidation validation)
+ {
+ RegisterFunction(SpatialDialect.HqlPrefix + validation, new SpatialValidationFunction(this, validation));
+ }
+
+ private void RegisterSpatialFunction(SpatialAnalysis analysis)
+ {
+ RegisterFunction(SpatialDialect.HqlPrefix + analysis, new SpatialAnalysisFunction(this, analysis));
+ }
+
+ #endregion
+ }
+}
diff --git a/NHibernate.Spatial.SpatiaLite/Driver/SpatiaLiteDriver.cs b/NHibernate.Spatial.SpatiaLite/Driver/SpatiaLiteDriver.cs
new file mode 100644
index 0000000..7c9bbb6
--- /dev/null
+++ b/NHibernate.Spatial.SpatiaLite/Driver/SpatiaLiteDriver.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Data;
+using System.Data.Common;
+using System.Data.SQLite;
+using System.IO;
+using System.Reflection;
+using NHibernate.Driver;
+
+namespace NHibernate.Spatial.Driver
+{
+ public class SpatiaLiteDriver : SQLite20Driver
+ {
+ static SpatiaLiteDriver()
+ {
+ string path = Environment.GetEnvironmentVariable("PATH");
+ if (path == null)
+ {
+ throw new InvalidOperationException("Cannot get PATH environment variable");
+ }
+
+ string executingDirectory = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().EscapedCodeBase).LocalPath);
+ if (string.IsNullOrEmpty(executingDirectory))
+ {
+ throw new InvalidOperationException("Cannot get executing directory");
+ }
+
+ string spatiaLitePath = Path.Combine(executingDirectory, "lib", "spatialite");
+ if (Directory.Exists(spatiaLitePath) && !path.ToLower().Contains(spatiaLitePath))
+ {
+ Environment.SetEnvironmentVariable("PATH", spatiaLitePath + Path.PathSeparator + path);
+ }
+ }
+
+ public override DbConnection CreateConnection()
+ {
+ var cn = base.CreateConnection();
+ cn.StateChange += ConnectionStateChangeHandler;
+ return cn;
+ }
+
+ private static void ConnectionStateChangeHandler(object sender, StateChangeEventArgs e)
+ {
+ if (e.OriginalState != ConnectionState.Broken &&
+ e.OriginalState != ConnectionState.Closed &&
+ e.OriginalState != ConnectionState.Connecting ||
+ e.CurrentState != ConnectionState.Open)
+ {
+ return;
+ }
+
+ var connection = (SQLiteConnection) sender;
+ using (var command = connection.CreateCommand())
+ {
+ command.CommandText = "PRAGMA foreign_keys = ON;";
+ command.ExecuteNonQuery();
+
+ // NOTE: After upgrading System.Data.SQLite from 1.0.98.1 to 1.0.116.0, the
+ // "SELECT load_extension('mod_spatialite');" SQL query to load the
+ // extension failed with a "SQL logic error: not authorized" error.
+ // Therefore, need to use the SQLiteConnection method instead; see:
+ // https://www.sqlite.org/loadext.html
+ connection.LoadExtension("mod_spatialite");
+
+ command.CommandText = "SELECT InitSpatialMetadata(1) WHERE CheckSpatialMetadata() = 0;";
+ command.ExecuteNonQuery();
+ }
+ }
+ }
+}
diff --git a/NHibernate.Spatial.SpatiaLite/Metadata/GeometryColumn.SpatiaLiteDialect.hbm.xml b/NHibernate.Spatial.SpatiaLite/Metadata/GeometryColumn.SpatiaLiteDialect.hbm.xml
new file mode 100644
index 0000000..daf3b85
--- /dev/null
+++ b/NHibernate.Spatial.SpatiaLite/Metadata/GeometryColumn.SpatiaLiteDialect.hbm.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/NHibernate.Spatial.SpatiaLite/Metadata/SpatiaLiteGeometryColumn.cs b/NHibernate.Spatial.SpatiaLite/Metadata/SpatiaLiteGeometryColumn.cs
new file mode 100644
index 0000000..aa8600c
--- /dev/null
+++ b/NHibernate.Spatial.SpatiaLite/Metadata/SpatiaLiteGeometryColumn.cs
@@ -0,0 +1,75 @@
+using System;
+
+namespace NHibernate.Spatial.Metadata
+{
+ public class SpatiaLiteGeometryColumn : GeometryColumn
+ {
+ ///
+ /// Gets or sets the geometry subtype.
+ ///
+ /// The subtype.
+ public override string Subtype
+ {
+ get
+ {
+ switch (GeometryType)
+ {
+ case 1:
+ return "POINT";
+ case 2:
+ return "LINESTRING";
+ case 3:
+ return "POLYGON";
+ case 4:
+ return "MULTIPOINT";
+ case 5:
+ return "MULTILINESTRING";
+ case 6:
+ return "MULTIPOLYGON";
+ case 7:
+ return "GEOMETRYCOLLECTION";
+ }
+ throw new Exception("Should never reach here");
+ }
+ set
+ {
+ switch (value)
+ {
+ case "POINT":
+ GeometryType = 1;
+ break;
+ case "LINESTRING":
+ GeometryType = 2;
+ break;
+ case "POLYGON":
+ GeometryType = 3;
+ break;
+ case "MULTIPOINT":
+ GeometryType = 4;
+ break;
+ case "MULTILINESTRING":
+ GeometryType = 5;
+ break;
+ case "MULTIPOLYGON":
+ GeometryType = 6;
+ break;
+ case "GEOMETRYCOLLECTION":
+ GeometryType = 7;
+ break;
+ default:
+ throw new Exception("Should never reach here");
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the integer value defining the .
+ ///
+ public virtual int GeometryType { get; set; }
+
+ ///
+ /// Gets or sets a value indicating which (if any) spatial index is enabled.
+ ///
+ public virtual int SpatialIndex { get; set; }
+ }
+}
diff --git a/NHibernate.Spatial.SpatiaLite/Metadata/SpatiaLiteSpatialReferenceSystem.cs b/NHibernate.Spatial.SpatiaLite/Metadata/SpatiaLiteSpatialReferenceSystem.cs
new file mode 100644
index 0000000..96423a9
--- /dev/null
+++ b/NHibernate.Spatial.SpatiaLite/Metadata/SpatiaLiteSpatialReferenceSystem.cs
@@ -0,0 +1,62 @@
+using System;
+
+namespace NHibernate.Spatial.Metadata
+{
+ ///
+ ///
+ ///
+ [Serializable]
+ public class SpatiaLiteSpatialReferenceSystem : SpatialReferenceSystem
+ {
+ private string _refSysName;
+ private string _proj4Text;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SpatiaLiteSpatialReferenceSystem()
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The SRID.
+ /// Name of the authority.
+ /// The authority srid.
+ /// The spatial reference system name.
+ /// The PROJ.4 text.
+ /// The well known text.
+ public SpatiaLiteSpatialReferenceSystem(int srid,
+ string authorityName,
+ int authoritySrid,
+ string refSysName,
+ string proj4Text,
+ string wellKnownText)
+ {
+ SRID = srid;
+ AuthorityName = authorityName;
+ AuthoritySRID = authoritySrid;
+ RefSysName = refSysName;
+ Proj4Text = proj4Text;
+ WellKnownText = wellKnownText;
+ }
+
+ ///
+ /// Gets or sets the spatial reference system name.
+ ///
+ public virtual string RefSysName
+ {
+ get => _refSysName;
+ set => _refSysName = value;
+ }
+
+ ///
+ /// Gets or sets the PROJ.4 text.
+ ///
+ public virtual string Proj4Text
+ {
+ get => _proj4Text;
+ set => _proj4Text = value;
+ }
+ }
+}
diff --git a/NHibernate.Spatial.SpatiaLite/Metadata/SpatialReferenceSystem.SpatiaLiteDialect.hbm.xml b/NHibernate.Spatial.SpatiaLite/Metadata/SpatialReferenceSystem.SpatiaLiteDialect.hbm.xml
new file mode 100644
index 0000000..9ff7b87
--- /dev/null
+++ b/NHibernate.Spatial.SpatiaLite/Metadata/SpatialReferenceSystem.SpatiaLiteDialect.hbm.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/NHibernate.Spatial.SpatiaLite/NHibernate.Spatial.SpatiaLite.csproj b/NHibernate.Spatial.SpatiaLite/NHibernate.Spatial.SpatiaLite.csproj
new file mode 100644
index 0000000..ef897e5
--- /dev/null
+++ b/NHibernate.Spatial.SpatiaLite/NHibernate.Spatial.SpatiaLite.csproj
@@ -0,0 +1,27 @@
+
+
+
+
+ netstandard2.0
+ NHibernate.Spatial
+ true
+ true
+ snupkg
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/NHibernate.Spatial.SpatiaLite/Type/SpatiaLiteGeometryType.cs b/NHibernate.Spatial.SpatiaLite/Type/SpatiaLiteGeometryType.cs
new file mode 100644
index 0000000..1ffcc9d
--- /dev/null
+++ b/NHibernate.Spatial.SpatiaLite/Type/SpatiaLiteGeometryType.cs
@@ -0,0 +1,73 @@
+using System;
+using NetTopologySuite.Geometries;
+using NetTopologySuite.IO;
+
+namespace NHibernate.Spatial.Type
+{
+ [Serializable]
+ public class SpatiaLiteGeometryType : GeometryTypeBase
+ {
+ private readonly GaiaGeoReader _reader;
+ private readonly GaiaGeoWriter _writer;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SpatiaLiteGeometryType()
+ : base(NHibernateUtil.BinaryBlob)
+ {
+ _reader = new GaiaGeoReader();
+ _writer = new GaiaGeoWriter();
+ }
+
+ ///
+ /// Convert a to a SpatiaLite byte array.
+ ///
+ ///
+ ///
+ protected override byte[] FromGeometry(object value)
+ {
+ var geometry = value as Geometry;
+ if (geometry == null)
+ {
+ return null;
+ }
+
+ SetDefaultSRID(geometry);
+
+ // Ensure HandleOrdinates is set according to the ordinality of the geometry
+ // https://github.com/NetTopologySuite/NetTopologySuite.IO.SpatiaLite/issues/7
+ var ordinates = Ordinates.XY;
+ var coordinate = geometry.Coordinate;
+ if (coordinate != null && !double.IsNaN(coordinate.Z))
+ {
+ ordinates |= Ordinates.Z;
+ }
+ if (coordinate != null && !double.IsNaN(coordinate.M))
+ {
+ ordinates |= Ordinates.M;
+ }
+ _writer.HandleOrdinates = ordinates;
+
+ return _writer.Write(geometry);
+ }
+
+ ///
+ /// Convert a SpatiaLite byte array to a .
+ ///
+ ///
+ ///
+ protected override Geometry ToGeometry(object value)
+ {
+ if (!(value is byte[] bytes))
+ {
+ return null;
+ }
+
+ var geometry = _reader.Read(bytes);
+ SetDefaultSRID(geometry);
+
+ return geometry;
+ }
+ }
+}
diff --git a/NHibernate.Spatial.sln b/NHibernate.Spatial.sln
index 3a1198a..b7475c3 100644
--- a/NHibernate.Spatial.sln
+++ b/NHibernate.Spatial.sln
@@ -30,6 +30,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.NHibernate.Spatial.My
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.NHibernate.Spatial.PostGis30", "Tests.NHibernate.Spatial.PostGis30\Tests.NHibernate.Spatial.PostGis30.csproj", "{ED1D3C35-D6F7-44CD-88A6-8E9192F685A8}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NHibernate.Spatial.SpatiaLite", "NHibernate.Spatial.SpatiaLite\NHibernate.Spatial.SpatiaLite.csproj", "{83520F4F-5F22-4AA8-B5AA-9C38C742486E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.NHibernate.Spatial.SpatiaLite", "Tests.NHibernate.Spatial.SpatiaLite\Tests.NHibernate.Spatial.SpatiaLite.csproj", "{38892858-02D0-4C5F-BAF6-891D5BFD67DF}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -80,6 +84,14 @@ Global
{ED1D3C35-D6F7-44CD-88A6-8E9192F685A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED1D3C35-D6F7-44CD-88A6-8E9192F685A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED1D3C35-D6F7-44CD-88A6-8E9192F685A8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {83520F4F-5F22-4AA8-B5AA-9C38C742486E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {83520F4F-5F22-4AA8-B5AA-9C38C742486E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {83520F4F-5F22-4AA8-B5AA-9C38C742486E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {83520F4F-5F22-4AA8-B5AA-9C38C742486E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {38892858-02D0-4C5F-BAF6-891D5BFD67DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {38892858-02D0-4C5F-BAF6-891D5BFD67DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {38892858-02D0-4C5F-BAF6-891D5BFD67DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {38892858-02D0-4C5F-BAF6-891D5BFD67DF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/README.md b/README.md
index 0cea30e..d7032cb 100644
--- a/README.md
+++ b/README.md
@@ -6,19 +6,21 @@ NHibernate binaries.
## NuGet Packages
-| Package | Version |
-|-----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [NHibernate.Spatial.MsSql](https://www.nuget.org/packages/NHibernate.Spatial.MsSql) | [![NuGet Status](http://img.shields.io/nuget/v/NHibernate.Spatial.MsSql.svg?style=flat)](http://www.nuget.org/packages/NHibernate.Spatial.MsSql) |
-| [NHibernate.Spatial.MySQL](https://www.nuget.org/packages/NHibernate.Spatial.MySQL) | [![NuGet Status](http://img.shields.io/nuget/v/NHibernate.Spatial.MySQL.svg?style=flat)](http://www.nuget.org/packages/NHibernate.Spatial.MySQL) |
-| [NHibernate.Spatial.PostGis](https://www.nuget.org/packages/NHibernate.Spatial.PostGis) | [![NuGet Status](http://img.shields.io/nuget/v/NHibernate.Spatial.PostGis.svg?style=flat)](http://www.nuget.org/packages/NHibernate.Spatial.PostGis) |
+| Package | Version |
+|-----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [NHibernate.Spatial.MsSql](https://www.nuget.org/packages/NHibernate.Spatial.MsSql) | [![NuGet Status](http://img.shields.io/nuget/v/NHibernate.Spatial.MsSql.svg?style=flat)](http://www.nuget.org/packages/NHibernate.Spatial.MsSql) |
+| [NHibernate.Spatial.MySQL](https://www.nuget.org/packages/NHibernate.Spatial.MySQL) | [![NuGet Status](http://img.shields.io/nuget/v/NHibernate.Spatial.MySQL.svg?style=flat)](http://www.nuget.org/packages/NHibernate.Spatial.MySQL) |
+| [NHibernate.Spatial.PostGis](https://www.nuget.org/packages/NHibernate.Spatial.PostGis) | [![NuGet Status](http://img.shields.io/nuget/v/NHibernate.Spatial.PostGis.svg?style=flat)](http://www.nuget.org/packages/NHibernate.Spatial.PostGis) |
+| [NHibernate.Spatial.SpatiaLite](https://www.nuget.org/packages/NHibernate.Spatial.SpatiaLite) | [![NuGet Status](http://img.shields.io/nuget/v/NHibernate.Spatial.SpatiaLite.svg?style=flat)](http://www.nuget.org/packages/NHibernate.Spatial.SpatiaLite) |
## Supported Databases
-| Package | Dialects | CI Tests |
-|----------------------------|--------------------------|---------------------------------------------------------------------------------------|
-| NHibernate.Spatial.MsSql | SQL Server 2012 | SQL Server 2017, SQL Server 2019, SQL Server 2022 |
-| NHibernate.Spatial.MySQL | MySQL 5.7, MySQL 8.0 | MySQL 5.7, MySQL 8.0, MySQL 8.3 |
-| NHibernate.Spatial.PostGis | PostGIS 2.0, PostGIS 3.0 | PostGIS 2.5 (PostgreSQL 12), PostGIS 3.0 (PostgreSQL 12), PostGIS 3.4 (PostgreSQL 16) |
+| Package | Dialects | CI Tests |
+|-------------------------------|--------------------------|---------------------------------------------------------------------------------------|
+| NHibernate.Spatial.MsSql | SQL Server 2012 | SQL Server 2017, SQL Server 2019, SQL Server 2022 |
+| NHibernate.Spatial.MySQL | MySQL 5.7, MySQL 8.0 | MySQL 5.7, MySQL 8.0, MySQL 8.3 |
+| NHibernate.Spatial.PostGis | PostGIS 2.0, PostGIS 3.0 | PostGIS 2.5 (PostgreSQL 12), PostGIS 3.0 (PostgreSQL 12), PostGIS 3.4 (PostgreSQL 16) |
+| NHibernate.Spatial.SpatiaLite | SpatiaLite | SpatiaLite 4, SpatiaLite 5 |
## Getting Started
@@ -32,6 +34,15 @@ For general NHibernate help, the NHibernate community website - /lib/spatialite` to the
+`PATH`.
+
+https://www.gaia-gis.it/fossil/libspatialite/wiki?name=mod_spatialite
+
## Discussion Forum
Discussion is made through the [NHibernate.Spatial Mailing List](https://groups.google.com/forum/#!forum/nhibernate-spatial).
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Data/general/TestSimple.xml b/Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Data/general/TestSimple.xml
new file mode 100644
index 0000000..f2d3c45
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Data/general/TestSimple.xml
@@ -0,0 +1,294 @@
+
+
+
+
+ P - point
+
+ POINT(10 10)
+
+
+
+ true
+
+
+
+
+
+ mP - multipoint with repeated points
+
+ MULTIPOINT (80 280, 80 220, 160 220, 80 220)
+
+
+
+ false
+
+
+
+
+
+ mP - multipoint with no repeated points
+
+ MULTIPOINT (80 280, 80 220, 160 220)
+
+
+
+ true
+
+
+
+
+
+ mP - empty
+
+ MULTIPOINT EMPTY
+
+
+
+ false
+
+
+
+
+
+ L - simple line
+
+ LINESTRING(10 10, 20 20)
+
+
+
+ true
+
+
+
+
+
+ L - non-simple, proper interior intersection
+
+ LINESTRING (20 60, 160 60, 80 160, 80 20)
+
+
+
+ false
+
+
+
+
+
+ L - non-simple, interior intersection at vertices
+
+ LINESTRING (20 80, 80 20, 80 80, 140 60, 80 20, 160 20)
+
+
+
+ false
+
+
+
+
+
+ L - non-simple, interior intersection at Bdy/non-vertex
+
+ LINESTRING (20 60, 100 60, 60 100, 60 60)
+
+
+
+ false
+
+
+
+
+
+ L - non-simple, interior intersection at Bdy/vertex
+
+ LINESTRING (20 60, 60 60, 100 60, 60 100, 60 60)
+
+
+
+ false
+
+
+
+
+
+ L - simple, intersection at Bdy/Bdy (ring)
+
+ LINESTRING (20 20, 80 20, 80 80, 20 20)
+
+
+
+ true
+
+
+
+
+
+ L - simple, intersection at Bdy/Bdy + non-vertex
+
+ LINESTRING (80 80, 20 20, 20 80, 140 80, 140 140, 80 80)
+
+
+
+ false
+
+
+
+
+
+ L - empty
+
+ LINESTRING EMPTY
+
+
+
+ false
+
+
+
+
+
+ mL - intersection between elements at non-vertex
+
+ MULTILINESTRING(
+ (40 140, 160 40),
+ (160 140, 40 40))
+
+
+
+ false
+
+
+
+
+
+ mL - no intersection between elements
+
+ MULTILINESTRING(
+ (20 160, 20 20),
+ (100 160, 100 20))
+
+
+
+ true
+
+
+
+
+
+ mL - mutual intersection at endpoints only
+
+ MULTILINESTRING ((60 140, 20 80, 60 40),
+ (60 40, 100 80, 60 140))
+
+
+
+ true
+
+
+
+
+
+ mL - one element is non-simple
+
+ MULTILINESTRING ((60 40, 140 40, 100 120, 100 0),
+ (100 200, 200 120))
+
+
+
+ false
+
+
+
+
+
+ mL - proper intersection between elements at vertex
+
+ MULTILINESTRING ((40 120, 100 60),
+ (160 120, 100 60),
+ (40 60, 160 60))
+
+
+
+ false
+
+
+
+
+
+ mL - intersection between closed lines
+
+ MULTILINESTRING ((80 160, 40 220, 40 100, 80 160),
+ (80 160, 120 220, 120 100, 80 160))
+
+
+
+ false
+
+
+
+
+
+ mL - intersection between closed and open lines
+
+ MULTILINESTRING ((80 160, 40 220),
+ (80 160, 120 220, 120 100, 80 160),
+ (40 100, 80 160))
+
+
+
+ false
+
+
+
+
+
+ A
+
+ POLYGON ((180 260, 80 300, 40 180, 160 120, 180 260))
+
+
+
+ true
+
+
+
+
+
+ A - empty
+
+ POLYGON EMPTY
+
+
+
+ false
+
+
+
+
+
+ mA
+
+ MULTIPOLYGON (((240 160, 140 220, 80 60, 220 40, 240 160)),
+ ((160 380, 100 240, 20 380, 160 380),
+ (120 340, 60 360, 80 320, 120 340)))
+
+
+
+ true
+
+
+
+
+
+ mA - with touching elements
+
+ MULTIPOLYGON (((240 160, 100 240, 80 60, 220 40, 240 160)),
+ ((160 380, 100 240, 20 380, 160 380),
+ (120 340, 60 360, 80 320, 120 340)))
+
+
+
+ true
+
+
+
+
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Data/general/TestValid.xml b/Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Data/general/TestValid.xml
new file mode 100644
index 0000000..c7098d9
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Data/general/TestValid.xml
@@ -0,0 +1,678 @@
+
+
+
+
+ L - linear-ring bowtie
+ LINEARRING(0 0, 100 100, 100 0, 0 100, 0 0)
+
+ false
+
+
+
+
+ L - linestring bowtie
+ LINESTRING(0 0, 100 100, 100 0, 0 100, 0 0)
+
+ true
+
+
+
+
+ P - point
+
+ POINT(10 10)
+
+
+
+ true
+
+
+
+
+
+ mP - no repeated points
+
+ MULTIPOINT(10 10, 20 20, 30 30)
+
+
+ true
+
+
+
+
+ P - repeated points
+
+ MULTIPOINT(10 10, 20 20, 30 30, 10 10)
+
+
+ true
+
+
+
+
+ L - no repeated points
+
+ LINESTRING (40 180, 120 120, 140 200, 200 140, 240 200)
+
+
+ true
+
+
+
+
+ L - repeated points
+
+ LINESTRING (40 180, 120 120, 140 200, 140 200, 200 140, 240 200)
+
+
+ true
+
+
+
+
+ L - linestring with two identical points
+ LINESTRING(0 0, 0 0)
+
+ false
+
+
+
+
+ A - zero-area polygon
+ POLYGON ((0 0, 0 0, 0 0, 0 0, 0 0))
+
+ false
+
+
+
+
+ A - polygon with too few points
+ POLYGON ((0 0, 10 0, 20 0, 0 0, 0 0))
+
+ false
+
+
+
+
+ A - polygon with repeated point
+ POLYGON ((107 246, 107 246, 250 285, 294 137, 151 90, 15 125, 157 174, 107 246))
+
+ true
+
+
+
+
+ A - polygon with degenerate hole ring (A-B-A)
+
+ POLYGON ((0 0, 0 240, 260 240, 260 0, 0 0),
+ (220 200, 40 200, 40 20, 40 200, 220 200, 220 200))
+
+
+ false
+
+
+
+
+ mA - multipolygon with component with too few points
+
+ MULTIPOLYGON ( ((100 20, 180 20, 180 100, 100 100, 100 20)),
+ ((20 100, 100 100, 100 180, 20 180, 20 100)),
+ ((100 180, 180 180, 180 260, 100 260, 100 180)),
+ ((180 100, 180 180, 180 180, 180 100)))
+
+
+ false
+
+
+
+
+ A - polygon self-intersects at non-vertex
+ POLYGON ((0 40, 0 0, 40 40, 40 0, 0 40))
+
+ false
+
+
+
+ A - polygon self-intersects at vertex
+ MULTIPOLYGON ( ((0 40, 20 20, 40 0, 40 40, 20 20, 0 0, 0 40)) )
+
+ false
+
+
+
+ A - polygon self-intersects at vertex/non-vertex
+ POLYGON ((0 40, 20 20, 40 0, 40 40, 0 0, 0 40))
+
+ false
+
+
+
+ A - hole self-intersects at non-vertex
+ POLYGON ((-10 50, 50 50, 50 -10, -10 -10, -10 50), (0 40, 0 0, 40 40, 40 0, 0 40))
+
+ false
+
+
+
+ A - polygon self-intersects at vertex
+ POLYGON ((-10 50, 50 50, 50 -10, -10 -10, -10 50), (0 40, 20 20, 40 0, 40 40, 20 20, 0 0, 0 40))
+
+ false
+
+
+
+ A - polygon self-intersects at vertex/non-vertex
+ POLYGON ((-10 50, 50 50, 50 -10, -10 -10, -10 50), (0 40, 20 20, 40 0, 40 40, 0 0, 0 40))
+
+ false
+
+
+
+ A - Valid doughnut
+ POLYGON ((0 60, 0 0, 60 0, 60 60, 0 60), (20 40, 20 20, 40 20, 40 40, 20 40))
+
+ true
+
+
+
+ A - shell has repeated points
+ POLYGON ((0 60, 0 0, 0 0, 60 0, 60 60, 0 60), (20 40, 20 20, 40 20, 40 40, 20 40))
+
+ true
+
+
+
+
+ A - shell touches hole without crossing it (valid)
+ POLYGON ((0 60, 0 0, 60 0, 60 60, 0 60), (20 40, 20 20, 60 20, 20 40))
+
+ true
+
+
+
+ A - shell touches hole without crossing it, but does so twice (invalid)
+ POLYGON ((0 60, 0 0, 60 0, 60 60, 0 60), (0 40, 20 20, 60 20, 0 40))
+
+ false
+
+
+
+ A - hole touches hole without crossing it (valid)
+ POLYGON ((0 120, 0 0, 140 0, 140 120, 0 120), (100 100, 100 20, 120 20, 120 100, 100 100), (20 100, 20 40, 100 40, 20 100))
+
+ true
+
+
+
+ A - holel touches hole without crossing it, but does so twice (invalid)
+
+ POLYGON ((0 120, 0 0, 140 0, 140 120, 0 120),
+ (100 100, 100 20, 120 20, 120 100, 100 100),
+ (20 100, 20 40, 100 40, 80 60, 100 80, 20 100))
+
+
+ false
+
+
+
+ A - hole touches hole without crossing it, but does so at an infinite number of points (invalid)
+
+ POLYGON ((0 120, 0 0, 140 0, 140 120, 0 120),
+ (100 100, 100 20, 120 20, 120 100, 100 100),
+ (20 100, 20 40, 100 40, 100 80, 20 100))
+
+
+ false
+
+
+
+ A - spike (invalid)
+ POLYGON ((0 60, 0 0, 60 0, 60 20, 100 20, 60 20, 60 60, 0 60))
+
+ false
+
+
+
+ A - puncture (invalid)
+ POLYGON ((0 60, 0 0, 60 0, 60 20, 20 20, 60 20, 60 60, 0 60))
+
+ false
+
+
+
+ A - hole within a hole (invalid)
+ POLYGON ((0 140, 0 0, 180 0, 180 140, 0 140), (20 20, 160 20, 160 120, 20 120, 20 20), (40 100, 40 40, 140 40, 140 100, 40 100))
+
+ false
+
+
+
+ A - empty shell and holes (valid)
+ POLYGON (EMPTY, EMPTY, EMPTY)
+
+ false
+
+
+
+
+ A - hole overlapping shell at non-vertex
+
+ POLYGON ((60 280, 260 180, 60 80, 60 280),
+ (140 80, 120 180, 200 180, 140 80))
+
+
+
+ false
+
+
+
+
+
+ A - shell self-overlaps
+
+ POLYGON ((60 340, 60 100, 340 100, 340 280, 340 200, 340 340, 60 340))
+
+
+ false
+
+
+
+
+ A - hole with repeated points
+
+ POLYGON ((40 260, 40 60, 120 60, 180 160, 240 60, 300 60, 300 260, 40 260),
+ (70 230, 80 230, 80 220, 80 220, 70 230))
+
+
+
+ true
+
+
+
+
+ A - hole outside but adjacent to shell
+
+ POLYGON ((40 260, 40 60, 120 60, 180 160, 240 60, 300 60, 300 260, 40 260),
+ (180 160, 240 60, 120 60, 180 160))
+
+
+
+ false
+
+
+
+
+
+ A - hole touches shell at two points
+
+ POLYGON ((240 260, 40 260, 40 80, 240 80, 240 260),
+ (140 180, 40 180, 140 260, 140 180))
+
+
+
+ false
+
+
+
+
+
+ A - hole touches shell at one non-vertex point
+
+ POLYGON ((240 260, 40 260, 40 80, 240 80, 240 260),
+ (140 180, 40 180, 140 240, 140 180))
+
+
+ true
+
+
+
+
+ A - hole touches shell at one vertex point
+
+ POLYGON ((240 260, 40 260, 40 80, 240 80, 240 260),
+ (140 180, 40 260, 140 240, 140 180))
+
+
+ true
+
+
+
+
+ A - hole outside shell
+
+ POLYGON ((20 180, 20 20, 140 20, 140 180, 20 180),
+ (160 120, 180 100, 160 80, 160 120))
+
+
+
+ false
+
+
+
+
+
+ A - hole identical to shell
+
+ POLYGON ((20 180, 20 20, 140 20, 140 180, 20 180),
+ (20 180, 20 20, 140 20, 140 180, 20 180))
+
+
+
+ false
+
+
+
+
+
+ A - hole identical to shell
+
+ POLYGON ((20 180, 20 20, 140 20, 140 180, 20 180),
+ (20 180, 20 20, 140 20, 140 180, 20 180))
+
+
+
+ false
+
+
+
+
+
+ A - hole self-intersects
+
+ POLYGON ((380 340, 40 340, 40 20, 380 20, 380 340),
+ (120 300, 300 280, 320 200, 160 140, 200 80, 320 120, 320 200, 360 60, 120 40, 120 300))
+
+
+ false
+
+
+
+
+ A - holes overlap, first point is identical
+
+ POLYGON ((20 320, 260 320, 260 20, 20 20, 20 320),
+ (140 280, 80 100, 200 100, 140 280),
+ (140 280, 40 80, 240 80, 140 280))
+
+
+ false
+
+
+
+
+ A - holes do not overlap, first point is identical
+
+ POLYGON ((20 320, 240 320, 240 40, 20 40, 20 320),
+ (140 180, 60 120, 60 240, 140 180),
+ (140 180, 200 120, 200 240, 140 180))
+
+
+ true
+
+
+
+
+ A - shell self-touches at vertex
+
+ POLYGON ((340 320, 340 200, 200 280, 200 80, 340 200, 340 20, 60 20, 60 340, 340 320))
+
+
+ false
+
+
+
+
+ A - shell self-touches at non-vertex
+
+ POLYGON ((300 320, 300 220, 260 260, 180 220, 360 220, 360 140, 120 140, 120 320, 300 320))
+
+
+ false
+
+
+
+
+ A - chain of holes surrounds an island inside the polygon
+
+ POLYGON ((40 300, 40 20, 280 20, 280 300, 40 300),
+ (120 240, 80 180, 160 220, 120 240),
+ (220 240, 160 220, 220 160, 220 240),
+ (160 100, 80 180, 100 80, 160 100),
+ (160 100, 220 160, 240 100, 160 100))
+
+
+ false
+
+
+
+
+ A - chain of holes splits polygon in two (touching at vertices)
+
+ POLYGON ((40 320, 340 320, 340 20, 40 20, 40 320),
+ (100 120, 40 20, 180 100, 100 120),
+ (200 200, 180 100, 240 160, 200 200),
+ (260 260, 240 160, 300 200, 260 260),
+ (300 300, 300 200, 340 320, 300 300))
+
+
+ false
+
+
+
+
+ A - chain of holes splits polygon in two (touching at non-vertex)
+
+ POLYGON ((40 320, 340 320, 340 20, 40 20, 40 320),
+ (100 120, 40 20, 180 100, 100 120),
+ (200 200, 180 100, 240 160, 200 200),
+ (260 260, 240 160, 300 200, 260 260),
+ (300 300, 300 200, 340 260, 300 300))
+
+
+ false
+
+
+
+
+ A - holes touch in one point
+
+ POLYGON ((190 190, 360 20, 20 20, 190 190),
+ (90 50, 150 110, 190 50, 90 50),
+ (190 50, 230 110, 290 50, 190 50))
+
+
+ true
+
+
+
+
+ A - holes touch in one point
+
+ POLYGON ((190 190, 360 20, 20 20, 190 190),
+ (90 50, 150 110, 190 50, 90 50),
+ (190 50, 230 110, 290 50, 190 50))
+
+
+ true
+
+
+
+
+ A - hole disconnects interiors
+
+ POLYGON ((0 0, 10 10, 10 0, 0 0),
+ (5 5, 5 0, 10 5, 5 5))
+
+
+ false
+
+
+
+
+ A - touching holes do NOT disconnect (isCCW bug)
+
+ POLYGON ((60 40, 60 240, 460 240, 460 40, 60 40),
+ (260 200, 340 60, 400 120, 260 200),
+ (260 200, 120 100, 200 60, 260 200))
+
+
+ true
+
+
+
+
+ mA - adjacent shells (shared vertices)
+
+ MULTIPOLYGON (((40 120, 140 120, 140 40, 40 40, 40 120)),
+ ((140 120, 40 120, 40 200, 140 200, 140 120)))
+
+
+
+ false
+
+
+
+
+
+ mA - adjacent shells (different vertices)
+
+ MULTIPOLYGON (((40 120, 140 120, 140 40, 40 40, 40 120)),
+ ((160 120, 60 120, 40 200, 140 200, 160 120)))
+
+
+
+ false
+
+
+
+
+
+ mA - nested overlapping shells
+
+ MULTIPOLYGON (((80 260, 240 260, 240 100, 80 100, 80 260)),
+ ((120 240, 220 240, 220 140, 120 140, 120 240)))
+
+
+ false
+
+
+
+
+ mA - nested non-overlapping shells
+
+ MULTIPOLYGON (((60 320, 60 80, 300 80, 60 320),
+ (80 280, 80 100, 260 100, 80 280)),
+ ((120 160, 140 160, 140 140, 120 160)))
+
+
+ true
+
+
+
+
+ mA - nested non-overlapping shells, all vertices touch
+
+ MULTIPOLYGON (((20 380, 420 380, 420 20, 20 20, 20 380),
+ (220 340, 180 240, 60 200, 180 160, 340 60, 240 220, 220 340)),
+ ((180 240, 180 160, 240 220, 180 240)))
+
+
+ true
+
+
+
+
+ mA - nested overlapping shells, all vertices touch
+
+ MULTIPOLYGON (((20 380, 420 380, 420 20, 20 20, 20 380),
+ (220 340, 180 240, 60 200, 140 100, 340 60, 300 240, 220 340)),
+ ((60 200, 340 60, 220 340, 60 200)))
+
+
+ false
+
+
+
+
+ mA - nested non-overlapping shells, all vertices touch
+
+ MULTIPOLYGON (((20 380, 420 380, 420 20, 20 20, 20 380),
+ (220 340, 80 320, 60 200, 140 100, 340 60, 300 240, 220 340)),
+ ((60 200, 340 60, 220 340, 60 200)))
+
+
+ true
+
+
+
+
+ mA - nested overlapping shells, all vertices touch
+
+ MULTIPOLYGON (((20 380, 420 380, 420 20, 20 20, 20 380),
+ (220 340, 180 240, 60 200, 200 180, 340 60, 240 220, 220 340)),
+ ((60 200, 340 60, 220 340, 60 200)))
+
+
+ false
+
+
+
+
+ mA - disconnected exterior
+
+ MULTIPOLYGON (((100 20, 180 20, 180 100, 100 100, 100 20)),
+ ((20 100, 100 100, 100 180, 20 180, 20 100)),
+ ((100 180, 180 180, 180 260, 100 260, 100 180)),
+ ((180 100, 260 100, 260 180, 180 180, 180 100)))
+
+
+ true
+
+
+
+
+ mA - shells touch in single point
+
+ MULTIPOLYGON (((110 110, 70 200, 150 200, 110 110)),
+ ((110 110, 150 20, 70 20, 110 110)))
+
+
+ true
+
+
+
+
+ mA - duplicate shells
+
+ MULTIPOLYGON (((60 300, 320 220, 260 60, 60 100, 60 300)),
+ ((60 300, 320 220, 260 60, 60 100, 60 300)))
+
+
+ false
+
+
+
+
+ mA - shells are not nested but share all vertices
+
+ MULTIPOLYGON (((180 60, 240 160, 300 60, 180 60)),
+ ((80 80, 180 60, 160 140, 240 160, 360 140, 300 60, 420 100, 320 280, 120 260, 80 80)))
+
+
+ true
+
+
+
+
+ mA - shell is nested inside first hole
+
+ MULTIPOLYGON (((0 0, 0 8, 8 8, 8 0, 0 0),
+ (3 3, 7 3, 7 7, 3 7, 3 3),
+ (1 1, 2 1, 2 2, 1 2, 1 1)),
+ ((4 4, 4 6, 6 6, 6 4, 4 4)))
+
+
+ true
+
+
+
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Model/NtsTestCase.cs b/Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Model/NtsTestCase.cs
new file mode 100644
index 0000000..f5b4c68
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Model/NtsTestCase.cs
@@ -0,0 +1,73 @@
+using NetTopologySuite.Geometries;
+using System;
+
+namespace Tests.NHibernate.Spatial.NtsTestCases.Model
+{
+ [Serializable]
+ public class NtsTestCase
+ {
+ private long id;
+
+ private string description;
+
+ private Geometry geometryA = GeometryCollection.Empty;
+
+ private Geometry geometryB = GeometryCollection.Empty;
+
+ private string operation;
+
+ private string parameter;
+
+ private Geometry geometryResult = GeometryCollection.Empty;
+
+ private bool booleanResult;
+
+ public virtual long Id
+ {
+ get => id;
+ set => id = value;
+ }
+
+ public virtual string Description
+ {
+ get => description;
+ set => description = value;
+ }
+
+ public virtual Geometry GeometryA
+ {
+ get => geometryA;
+ set => geometryA = value;
+ }
+
+ public virtual Geometry GeometryB
+ {
+ get => geometryB;
+ set => geometryB = value;
+ }
+
+ public virtual string Operation
+ {
+ get => operation;
+ set => operation = value;
+ }
+
+ public virtual string Parameter
+ {
+ get => parameter;
+ set => parameter = value;
+ }
+
+ public virtual Geometry GeometryResult
+ {
+ get => geometryResult;
+ set => geometryResult = value;
+ }
+
+ public virtual bool BooleanResult
+ {
+ get => booleanResult;
+ set => booleanResult = value;
+ }
+ }
+}
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Model/NtsTestCase.hbm.xml b/Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Model/NtsTestCase.hbm.xml
new file mode 100644
index 0000000..ee587a0
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Model/NtsTestCase.hbm.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+ -1
+ GEOMETRY
+
+
+
+
+ -1
+ GEOMETRY
+
+
+
+
+
+
+ -1
+ GEOMETRY
+
+
+
+
+
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteConformanceItemsFixture.cs b/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteConformanceItemsFixture.cs
new file mode 100644
index 0000000..71f293a
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteConformanceItemsFixture.cs
@@ -0,0 +1,112 @@
+using System.Linq;
+using NetTopologySuite.Geometries;
+using NHibernate.Cfg;
+using NHibernate.Spatial.Metadata;
+using NUnit.Framework;
+using Tests.NHibernate.Spatial.OgcSfSql11Compliance;
+using Tests.NHibernate.Spatial.OgcSfSql11Compliance.Model;
+
+namespace Tests.NHibernate.Spatial
+{
+ [TestFixture]
+ public class SpatiaLiteConformanceItemsFixture : ConformanceItemsFixture
+ {
+ [Test]
+ public override void ConformanceItemT05Hql()
+ {
+ if (!Metadata.SupportsSpatialMetadata(session, MetadataClass.SpatialReferenceSystem))
+ {
+ Assert.Ignore("Provider does not support spatial metadata");
+ }
+ var srs = session.CreateQuery(
+ "from SpatiaLiteSpatialReferenceSystem where SRID=101")
+ .UniqueResult();
+
+ Assert.IsNotNull(srs);
+ Assert.AreEqual(SpatialReferenceSystemWKT, srs.WellKnownText);
+
+ // Alternative syntax for identifiers:
+ srs = session.CreateQuery(
+ "from SpatiaLiteSpatialReferenceSystem s where s=101")
+ .UniqueResult();
+
+ Assert.IsNotNull(srs);
+ Assert.AreEqual(SpatialReferenceSystemWKT, srs.WellKnownText);
+ }
+
+ ///
+ /// Overridden because GetPointN is not zero-based in SpatiaLite
+ ///
+ [Test]
+ public override void ConformanceItemT23Linq()
+ {
+ var query =
+ from t in session.Query()
+ where t.Fid == 102
+ select ((LineString) t.Centerline)
+ .GetPointN(1);
+
+ Geometry geometry = query.Single();
+ var expected = Wkt.Read("POINT( 0 18 )");
+
+ Assert.IsTrue(expected.EqualsTopologically(geometry));
+ }
+
+ ///
+ /// Overridden because GetPointN is not zero-based in SpatiaLite
+ ///
+ [Test]
+ public override void ConformanceItemT29Linq()
+ {
+ var query =
+ from t in session.Query()
+ where t.Name == "Blue Lake"
+ select ((Polygon) t.Shore).GetInteriorRingN(1);
+
+ Geometry geometry = query.Single();
+ var expected = Wkt.Read("LINESTRING(59 18, 67 18, 67 13, 59 13, 59 18)");
+
+ Assert.IsTrue(expected.EqualsTopologically(geometry));
+ }
+
+ ///
+ /// Overridden because GetPointN is not zero-based in SpatiaLite
+ ///
+ [Test]
+ public override void ConformanceItemT31Linq()
+ {
+ var query =
+ from t in session.Query()
+ where t.Name == "Route 75"
+ select t.Centerlines.GetGeometryN(2);
+
+ var geometry = query.Single();
+ var expected = Wkt.Read("LINESTRING( 16 0, 16 23, 16 48 )");
+
+ Assert.IsTrue(expected.EqualsTopologically(geometry));
+ }
+
+ protected override void Configure(Configuration configuration)
+ {
+ TestConfiguration.Configure(configuration);
+ }
+
+ protected override void OnBeforeCreateSchema()
+ {
+ using (var session = sessions.OpenSession())
+ {
+ session.Save(new SpatiaLiteSpatialReferenceSystem(101, "POSC", 32214, "NHSP", "PROJ.4", SpatialReferenceSystemWKT));
+ session.Flush();
+ }
+ }
+
+ protected override void OnAfterDropSchema()
+ {
+ using (var session = sessions.OpenSession())
+ {
+ session.Delete("from SpatiaLiteSpatialReferenceSystem where SRID=101");
+ session.Flush();
+ }
+ }
+ }
+}
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteCriteriaFixture.cs b/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteCriteriaFixture.cs
new file mode 100644
index 0000000..192211c
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteCriteriaFixture.cs
@@ -0,0 +1,14 @@
+using NHibernate.Cfg;
+using NUnit.Framework;
+
+namespace Tests.NHibernate.Spatial
+{
+ [TestFixture]
+ public class SpatiaLiteCriteriaFixture : CriteriaFixture
+ {
+ protected override void Configure(Configuration configuration)
+ {
+ TestConfiguration.Configure(configuration);
+ }
+ }
+}
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteNtsTestCasesFixture.cs b/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteNtsTestCasesFixture.cs
new file mode 100644
index 0000000..82bf112
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteNtsTestCasesFixture.cs
@@ -0,0 +1,47 @@
+using System;
+using System.IO;
+using NHibernate.Cfg;
+using NUnit.Framework;
+using Tests.NHibernate.Spatial.NtsTestCases;
+using Tests.NHibernate.Spatial.NtsTestCases.Model;
+
+namespace Tests.NHibernate.Spatial
+{
+ [TestFixture]
+ public class SpatiaLiteNtsTestCasesFixture : NtsTestCasesFixture
+ {
+ private const string LocalDataPath = "../../../../Tests.NHibernate.Spatial.SpatiaLite/NtsTestCases/Data";
+
+ protected override string TestSimpleDataPath => Path.Combine(LocalDataPath, "general", "TestSimple.xml");
+
+ protected override string TestValidDataPath => Path.Combine(LocalDataPath, "general", "TestValid.xml");
+
+ protected override Type[] Mappings
+ {
+ get
+ {
+ return new[]
+ {
+#pragma warning disable 0436
+ typeof(NtsTestCase)
+#pragma warning restore 0436
+ };
+ }
+ }
+
+ [Test]
+ [Ignore("Not supported by SpatiaLite")]
+ public override void EqualsExact()
+ { }
+
+ [Test]
+ [Ignore("Not supported by SpatiaLite")]
+ public override void StringRelate()
+ { }
+
+ protected override void Configure(Configuration configuration)
+ {
+ TestConfiguration.Configure(configuration);
+ }
+ }
+}
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteProjectionsFixture.cs b/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteProjectionsFixture.cs
new file mode 100644
index 0000000..d13d7bd
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteProjectionsFixture.cs
@@ -0,0 +1,19 @@
+using NHibernate.Cfg;
+using NUnit.Framework;
+
+namespace Tests.NHibernate.Spatial
+{
+ [TestFixture]
+ public class SpatiaLiteProjectionsFixture : ProjectionsFixture
+ {
+ [Test]
+ [Ignore("SpatiaLite does not support Intersection spatial aggregate function")]
+ public override void IntersectionAll()
+ { }
+
+ protected override void Configure(Configuration config)
+ {
+ TestConfiguration.Configure(config);
+ }
+ }
+}
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteSpatialQueriesFixture.cs b/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteSpatialQueriesFixture.cs
new file mode 100644
index 0000000..e7b9c48
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/SpatiaLiteSpatialQueriesFixture.cs
@@ -0,0 +1,96 @@
+using NHibernate;
+using NHibernate.Cfg;
+using NUnit.Framework;
+using Tests.NHibernate.Spatial.RandomGeometries;
+
+namespace Tests.NHibernate.Spatial
+{
+ [TestFixture]
+ public class SpatiaLiteSpatialQueriesFixture : SpatialQueriesFixture
+ {
+ protected override void Configure(Configuration configuration)
+ {
+ TestConfiguration.Configure(configuration);
+ }
+
+ protected override string SqlLineStringFilter(string filterString)
+ {
+ return $@"
+ SELECT count(*)
+ FROM linestringtest
+ WHERE MbrIntersects(the_geom, ST_GeomFromText('{filterString}', 4326))
+ ";
+ }
+
+ protected override string SqlPolygonFilter(string filterString)
+ {
+ return $@"
+ SELECT count(*)
+ FROM polygontest
+ WHERE MbrIntersects(the_geom, ST_GeomFromText('{filterString}', 4326))
+ ";
+ }
+
+ protected override string SqlMultiLineStringFilter(string filterString)
+ {
+ return $@"
+ SELECT count(*)
+ FROM multilinestringtest
+ WHERE MbrIntersects(the_geom, ST_GeomFromText('{filterString}', 4326))
+ ";
+ }
+
+ protected override string SqlOvelapsLineString(string filterString)
+ {
+ return $@"
+ SELECT count(*)
+ FROM linestringtest
+ WHERE the_geom IS NOT NULL
+ AND ST_Overlaps(ST_PolygonFromText('{filterString}', 4326), the_geom)
+ ";
+ }
+
+ protected override string SqlIntersectsLineString(string filterString)
+ {
+ return $@"
+ SELECT count(*)
+ FROM linestringtest
+ WHERE the_geom IS NOT NULL
+ AND ST_Intersects(ST_PolygonFromText('{filterString}', 4326), the_geom)
+ ";
+ }
+
+ protected override ISQLQuery SqlIsEmptyLineString(ISession session)
+ {
+ return session.CreateSQLQuery(@"
+ SELECT ST_IsEmpty(the_geom) as result
+ FROM linestringtest
+ WHERE oid = ?
+ AND the_geom IS NOT NULL
+ ")
+ .AddScalar("result", NHibernateUtil.Boolean);
+ }
+
+ protected override ISQLQuery SqlIsSimpleLineString(ISession session)
+ {
+ return session.CreateSQLQuery(@"
+ SELECT ST_IsSimple(the_geom) as result
+ FROM linestringtest
+ WHERE oid = ?
+ AND the_geom IS NOT NULL
+ ")
+ .AddScalar("result", NHibernateUtil.Boolean);
+ }
+
+ protected override ISQLQuery SqlAsBinaryLineString(ISession session)
+ {
+ return session.CreateSQLQuery(@"
+ SELECT ST_AsBinary(the_geom) as result
+ FROM linestringtest
+ WHERE oid = ?
+ AND the_geom IS NOT NULL
+ ")
+ .AddScalar("result", NHibernateUtil.BinaryBlob);
+ }
+ }
+}
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/TestConfiguration.cs b/Tests.NHibernate.Spatial.SpatiaLite/TestConfiguration.cs
new file mode 100644
index 0000000..1ee83ba
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/TestConfiguration.cs
@@ -0,0 +1,42 @@
+using Microsoft.Extensions.Configuration;
+using NHibernate.Bytecode;
+using NHibernate.Cfg;
+using NHibernate.Spatial.Dialect;
+using NHibernate.Spatial.Driver;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Tests.NHibernate.Spatial
+{
+ internal static class TestConfiguration
+ {
+ private static readonly IConfigurationRoot _configurationRoot;
+
+ static TestConfiguration()
+ {
+ _configurationRoot = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json")
+ .Build();
+
+ try
+ {
+ File.Delete("nhsp_test.sqlite");
+ }
+ catch
+ { }
+ }
+
+ public static void Configure(Configuration configuration)
+ {
+ IDictionary properties = new Dictionary
+ {
+ [Environment.ProxyFactoryFactoryClass] = typeof(StaticProxyFactoryFactory).AssemblyQualifiedName,
+ [Environment.Dialect] = typeof(SpatiaLiteDialect).AssemblyQualifiedName,
+ [Environment.ConnectionProvider] = typeof(DebugConnectionProvider).AssemblyQualifiedName,
+ [Environment.ConnectionDriver] = typeof(SpatiaLiteDriver).AssemblyQualifiedName,
+ [Environment.ConnectionString] = _configurationRoot.GetConnectionString("SpatiaLite")
+ };
+ configuration.SetProperties(properties);
+ }
+ }
+}
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/Tests.NHibernate.Spatial.SpatiaLite.csproj b/Tests.NHibernate.Spatial.SpatiaLite/Tests.NHibernate.Spatial.SpatiaLite.csproj
new file mode 100644
index 0000000..a5480b9
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/Tests.NHibernate.Spatial.SpatiaLite.csproj
@@ -0,0 +1,35 @@
+
+
+
+ net6.0
+
+ false
+
+ Tests.NHibernate.Spatial
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ PreserveNewest
+
+
+
+
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/appsettings.json b/Tests.NHibernate.Spatial.SpatiaLite/appsettings.json
new file mode 100644
index 0000000..e49266d
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "SpatiaLite": "Data Source=nhsp_test.sqlite;Version=3;"
+ }
+}
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/README.md b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/README.md
new file mode 100644
index 0000000..8a4c789
--- /dev/null
+++ b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/README.md
@@ -0,0 +1,6 @@
+# SpatiaLite 5.1.0
+
+http://www.gaia-gis.it/gaia-sins/windows-bin-amd64
+
+> NB - These DLLs are only used for local testing on Windows. For testing on Linux (either locally
+ or as part of the CI build), the `libsqlite3-mod-spatialite` package should be installed.
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libcrypto-3-x64.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libcrypto-3-x64.dll
new file mode 100644
index 0000000..ba5f002
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libcrypto-3-x64.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libcurl-4.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libcurl-4.dll
new file mode 100644
index 0000000..76aea45
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libcurl-4.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libexpat-1.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libexpat-1.dll
new file mode 100644
index 0000000..3b7d9e6
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libexpat-1.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libfreexl-1.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libfreexl-1.dll
new file mode 100644
index 0000000..ce39a3e
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libfreexl-1.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libgcc_s_seh-1.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libgcc_s_seh-1.dll
new file mode 100644
index 0000000..fb2e767
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libgcc_s_seh-1.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libgeos.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libgeos.dll
new file mode 100644
index 0000000..d841298
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libgeos.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libgeos_c.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libgeos_c.dll
new file mode 100644
index 0000000..ccad769
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libgeos_c.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libiconv-2.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libiconv-2.dll
new file mode 100644
index 0000000..b20e68c
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libiconv-2.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libjpeg-62.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libjpeg-62.dll
new file mode 100644
index 0000000..0001d81
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libjpeg-62.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/liblzma-5.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/liblzma-5.dll
new file mode 100644
index 0000000..713a108
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/liblzma-5.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libminizip-1.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libminizip-1.dll
new file mode 100644
index 0000000..94e0c66
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libminizip-1.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libproj_9_2.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libproj_9_2.dll
new file mode 100644
index 0000000..92133fd
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libproj_9_2.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/librttopo-1.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/librttopo-1.dll
new file mode 100644
index 0000000..7985d62
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/librttopo-1.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libsharpyuv-0.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libsharpyuv-0.dll
new file mode 100644
index 0000000..4f23b5b
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libsharpyuv-0.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libsqlite3-0.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libsqlite3-0.dll
new file mode 100644
index 0000000..76f4bcf
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libsqlite3-0.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libssl-3-x64.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libssl-3-x64.dll
new file mode 100644
index 0000000..0d19a69
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libssl-3-x64.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libstdc++-6.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libstdc++-6.dll
new file mode 100644
index 0000000..2b2a3e9
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libstdc++-6.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libtiff-6.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libtiff-6.dll
new file mode 100644
index 0000000..5f3b936
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libtiff-6.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libwebp-7.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libwebp-7.dll
new file mode 100644
index 0000000..308a716
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libwebp-7.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libwinpthread-1.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libwinpthread-1.dll
new file mode 100644
index 0000000..c47b8da
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libwinpthread-1.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libxml2.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libxml2.dll
new file mode 100644
index 0000000..99d2792
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libxml2.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libzstd.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libzstd.dll
new file mode 100644
index 0000000..d2020f1
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/libzstd.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/mod_spatialite.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/mod_spatialite.dll
new file mode 100644
index 0000000..6b64fb9
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/mod_spatialite.dll differ
diff --git a/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/zlib1.dll b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/zlib1.dll
new file mode 100644
index 0000000..fccdbc6
Binary files /dev/null and b/Tests.NHibernate.Spatial.SpatiaLite/lib/spatialite/zlib1.dll differ
diff --git a/Tests.NHibernate.Spatial/OgcSfSql11Compliance/ConformanceItemsFixture.cs b/Tests.NHibernate.Spatial/OgcSfSql11Compliance/ConformanceItemsFixture.cs
index 27db22a..224e8c0 100644
--- a/Tests.NHibernate.Spatial/OgcSfSql11Compliance/ConformanceItemsFixture.cs
+++ b/Tests.NHibernate.Spatial/OgcSfSql11Compliance/ConformanceItemsFixture.cs
@@ -410,7 +410,7 @@ from g in session.Query()
///
///
[Test]
- public void ConformanceItemT05Hql()
+ public virtual void ConformanceItemT05Hql()
{
if (!Metadata.SupportsSpatialMetadata(session, MetadataClass.SpatialReferenceSystem))
{