diff --git a/BuildProcessTemplates/DefaultTemplate.11.1.xaml b/BuildProcessTemplates/DefaultTemplate.11.1.xaml
deleted file mode 100644
index 60eac4b..0000000
--- a/BuildProcessTemplates/DefaultTemplate.11.1.xaml
+++ /dev/null
@@ -1,543 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [New Microsoft.TeamFoundation.Build.Workflow.Activities.BuildSettings()]
- [False]
- [New Microsoft.TeamFoundation.Build.Workflow.Activities.TestSpecList(New Microsoft.TeamFoundation.Build.Workflow.Activities.AgileTestPlatformSpec("**\*test*.dll"))]
- ["$(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.r)"]
- [False]
- [True]
- [True]
- [Microsoft.TeamFoundation.Build.Workflow.Activities.CleanWorkspaceOption.All]
-
-
-
- [Microsoft.TeamFoundation.Build.Workflow.Activities.CodeAnalysisOption.AsConfigured]
- [True]
- [Microsoft.TeamFoundation.Build.Workflow.Activities.ToolPlatform.Auto]
- [True]
- [New Microsoft.TeamFoundation.Build.Workflow.Activities.SourceAndSymbolServerSettings(True, Nothing)]
- [True]
-
-
-
- [New Microsoft.TeamFoundation.Build.Workflow.Activities.AgentSettings() With {.MaxWaitTime = New System.TimeSpan(4, 0, 0), .MaxExecutionTime = New System.TimeSpan(0, 0, 0), .TagComparison = Microsoft.TeamFoundation.Build.Workflow.Activities.TagComparison.MatchExactly }]
- [Microsoft.TeamFoundation.Build.Workflow.BuildVerbosity.Normal]
-
-
-
-
-
-
- All
- 11.0
- Assembly references and imported namespaces serialized as XML namespaces
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BuildProcessTemplates/DefaultTemplate.xaml b/BuildProcessTemplates/DefaultTemplate.xaml
deleted file mode 100644
index eae035b..0000000
--- a/BuildProcessTemplates/DefaultTemplate.xaml
+++ /dev/null
@@ -1,602 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Assembly references and imported namespaces serialized as XML namespaces
-
-
- True
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- False
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- False
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- False
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- False
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- False
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/BuildProcessTemplates/LabDefaultTemplate.11.xaml b/BuildProcessTemplates/LabDefaultTemplate.11.xaml
deleted file mode 100644
index 542717f..0000000
--- a/BuildProcessTemplates/LabDefaultTemplate.11.xaml
+++ /dev/null
@@ -1,208 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 11.0
-
-
-
-
-
- 920,3702
- Assembly references and imported namespaces serialized as XML namespaces
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
-
- [LabWorkflowParameters.BuildDetails.BuildUri]
-
-
- [ChildBuildDetail.Uri]
-
-
-
-
-
-
-
-
-
-
-
- [BuildLocation]
-
-
- [If(LabWorkflowParameters.BuildDetails.Configuration Is Nothing, BuildLocation, If(LabWorkflowParameters.BuildDetails.Configuration.IsEmpty Or (SelectedBuildDetail.Information.GetNodesByType(Microsoft.TeamFoundation.Build.Common.InformationTypes.ConfigurationSummary, True)).Count = 1, BuildLocation, If(LabWorkflowParameters.BuildDetails.Configuration.IsPlatformEmptyOrAnyCpu, BuildLocation + "\" + LabWorkflowParameters.BuildDetails.Configuration.Configuration, BuildLocation + "\" + LabWorkflowParameters.BuildDetails.Configuration.Platform + "\" + LabWorkflowParameters.BuildDetails.Configuration.Configuration)))]
-
-
-
-
-
-
-
-
-
-
-
- [LabEnvironmentUri]
-
-
- [LabWorkflowParameters.EnvironmentDetails.LabEnvironmentUri.ToString()]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [PostDeploymentSnapshotName]
-
-
- [If(LabWorkflowParameters.BuildDetails.IsTeamSystemBuild = True,String.Format("{0}_{1}_{2}", LabWorkflowParameters.DeploymentDetails.PostDeploymentSnapshotName, BuildNumber,BuildDetail.BuildNumber),String.Format("{0}_{1}", LabWorkflowParameters.DeploymentDetails.PostDeploymentSnapshotName, BuildDetail.BuildNumber))]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [BuildStatus]
-
-
- [Microsoft.TeamFoundation.Build.Client.BuildStatus.PartiallySucceeded]
-
-
-
-
-
-
- [BuildStatus]
-
-
- [Microsoft.TeamFoundation.Build.Client.BuildStatus.Failed]
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/BuildProcessTemplates/UpgradeTemplate.xaml b/BuildProcessTemplates/UpgradeTemplate.xaml
deleted file mode 100644
index 4877aa3..0000000
--- a/BuildProcessTemplates/UpgradeTemplate.xaml
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [New Microsoft.TeamFoundation.Build.Workflow.Activities.AgentSettings() With {.MaxWaitTime = New System.TimeSpan(4, 0, 0), .MaxExecutionTime = New System.TimeSpan(0, 0, 0), .TagComparison = Microsoft.TeamFoundation.Build.Workflow.Activities.TagComparison.MatchExactly }]
-
-
-
- [Microsoft.TeamFoundation.Build.Workflow.Activities.ToolPlatform.Auto]
- [False]
- [False]
-
-
-
-
-
-
-
-
-
- [Microsoft.TeamFoundation.VersionControl.Client.RecursionType.OneLevel]
- [Microsoft.TeamFoundation.Build.Workflow.BuildVerbosity.Normal]
-
-
-
- All
- Assembly references and imported namespaces serialized as XML namespaces
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Functions.cs b/Functions.cs
deleted file mode 100644
index 4613e59..0000000
--- a/Functions.cs
+++ /dev/null
@@ -1,421 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-
-using System;
-using Microsoft.SqlServer.Types;
-using System.Data.SqlTypes;
-
-namespace SQLSpatialTools
-{
- // This class contains functions that can be registered in SQL Server.
- public class Functions
- {
- const double THRESHOLD = .01; // 1 cm tolerance in most SRIDs
-
- // Make our ShiftGeometrySink into a function call by hooking it into a simple pipeline.
- public static SqlGeometry ShiftGeometry(SqlGeometry g, double xShift, double yShift)
- {
- // create a sink that will create a geometry instance
- SqlGeometryBuilder b = new SqlGeometryBuilder();
-
- // create a sink to do the shift and plug it in to the builder
- ShiftGeometrySink s = new ShiftGeometrySink(xShift, yShift, b);
-
- // plug our sink into the geometry instance and run the pipeline
- g.Populate(s);
-
- // the end of our pipeline is now populated with the shifted geometry instance
- return b.ConstructedGeometry;
- }
-
- // Make our LocateAlongGeographySink into a function call. This function just hooks up
- // and runs a pipeline using the sink.
- public static SqlGeography LocateAlongGeog(SqlGeography g, double distance)
- {
- SqlGeographyBuilder b = new SqlGeographyBuilder();
- LocateAlongGeographySink p = new LocateAlongGeographySink(distance, b);
- g.Populate(p);
- return b.ConstructedGeography;
- }
-
- // Make our LocateAlongGeometrySink into a function call. This function just hooks up
- // and runs a pipeline using the sink.
- public static SqlGeometry LocateAlongGeom(SqlGeometry g, double distance)
- {
- SqlGeometryBuilder b = new SqlGeometryBuilder();
- LocateAlongGeometrySink p = new LocateAlongGeometrySink(distance, b);
- g.Populate(p);
- return b.ConstructedGeometry;
- }
-
- // Find the point that is the given distance from the start point in the direction of the end point.
- // The distance must be less than the distance between these two points.
- public static SqlGeography InterpolateBetweenGeog(SqlGeography start, SqlGeography end, double distance)
- {
- // We need to check a few prequisites.
-
- // We only operate on points.
-
- if (start.STGeometryType().Value != "Point")
- {
- throw new ArgumentException("Start value must be a point.");
- }
-
- if (end.STGeometryType().Value != "Point")
- {
- throw new ArgumentException("Start value must be a point.");
- }
-
- // The SRIDs also have to match
- int srid = start.STSrid.Value;
- if (srid != end.STSrid.Value)
- {
- throw new ArgumentException("The start and end SRIDs must match.");
- }
-
- // Finally, the distance has to fall between these points.
- if (distance > start.STDistance(end))
- {
- throw new ArgumentException("The distance value provided exceeds the distance between the two points.");
- }
- else if (distance < 0)
- {
- throw new ArgumentException("The distance must be positive.");
- }
-
- // We'll just do this by binary search---surely this could be more efficient, but this is
- // relatively easy.
- //
- // Note that we can't just take the take the linear combination of end vectors because we
- // aren't working on a sphere.
-
- // We are going to do our binary search using 3D Cartesian values, however
- Vector3 startCart = Util.GeographicToCartesian(start);
- Vector3 endCart = Util.GeographicToCartesian(end);
- Vector3 currentCart;
-
- SqlGeography current;
- double currentDistance;
-
- // Keep refining until we slip below the THRESHOLD value.
- do
- {
- currentCart = (startCart + endCart) / 2;
- current = Util.CartesianToGeographic(currentCart, srid);
- currentDistance = start.STDistance(current).Value;
- if (distance <= currentDistance) endCart = currentCart;
- else startCart = currentCart;
- } while (Math.Abs(currentDistance - distance) > THRESHOLD);
-
- return current;
- }
-
- // Find the point that is the given distance from the start point in the direction of the end point.
- // The distance must be less than the distance between these two points.
- public static SqlGeometry InterpolateBetweenGeom(SqlGeometry start, SqlGeometry end, double distance)
- {
- // We need to check a few prequisites.
-
- // We only operate on points.
-
- if (start.STGeometryType().Value != "Point")
- {
- throw new ArgumentException("Start value must be a point.");
- }
-
- if (end.STGeometryType().Value != "Point")
- {
- throw new ArgumentException("Start value must be a point.");
- }
-
- // The SRIDs also have to match
- int srid = start.STSrid.Value;
- if (srid != end.STSrid.Value)
- {
- throw new ArgumentException("The start and end SRIDs must match.");
- }
-
- // Finally, the distance has to fall between these points.
- double length = start.STDistance(end).Value;
- if (distance > start.STDistance(end))
- {
- throw new ArgumentException("The distance value provided exceeds the distance between the two points.");
- }
- else if (distance < 0)
- {
- throw new ArgumentException("The distance must be positive.");
- }
-
- // Since we're working on a Cartesian plane, this is now pretty simple.
- double f = distance/length; // The fraction of the way from start to end.
- double newX = (start.STX.Value * (1-f)) + (end.STX.Value * f);
- double newY = (start.STY.Value * (1-f)) + (end.STY.Value * f);
- return SqlGeometry.Point(newX, newY, srid);
- }
-
- // This function is used for generating a new geography object where additional points are inserted
- // along every line in such a way that the angle between two consecutive points does not
- // exceed a prescribed angle. The points are generated between the unit vectors that correspond
- // to the line's start and end along the great-circle arc on the unit sphere. This follows the
- // definition of geodetic lines in SQL Server.
- public static SqlGeography DensifyGeography(SqlGeography g, double maxAngle)
- {
- SqlGeographyBuilder b = new SqlGeographyBuilder();
- g.Populate(new DensifyGeographySink(b, maxAngle));
- return b.ConstructedGeography;
- }
-
- // This implements a completely trivial conversion from geometry to geography, simply taking each
- // point (x,y) --> (long, lat). The result is assigned the given SRID.
- public static SqlGeography VacuousGeometryToGeography(SqlGeometry toConvert, int targetSrid)
- {
- SqlGeographyBuilder b = new SqlGeographyBuilder();
- toConvert.Populate(new VacuousGeometryToGeographySink(targetSrid, b));
- return b.ConstructedGeography;
- }
-
- // This implements a completely trivial conversion from geography to geometry, simply taking each
- // point (lat,long) --> (y, x). The result is assigned the given SRID.
- public static SqlGeometry VacuousGeographyToGeometry(SqlGeography toConvert, int targetSrid)
- {
- SqlGeometryBuilder b = new SqlGeometryBuilder();
- toConvert.Populate(new VacuousGeographyToGeometrySink(targetSrid, b));
- return b.ConstructedGeometry;
- }
-
- // Computes ConvexHull of input geography and returns a polygon (unless all input points are colinear).
- //
- public static SqlGeography ConvexHullGeography(SqlGeography geography)
- {
- if (geography.IsNull || geography.STIsEmpty().Value) return geography;
-
- SqlGeography center = geography.EnvelopeCenter();
- SqlProjection gnomonicProjection = SqlProjection.Gnomonic(center.Long.Value, center.Lat.Value);
- SqlGeometry geometry = gnomonicProjection.Project(geography);
- return gnomonicProjection.Unproject(geometry.MakeValid().STConvexHull());
- }
-
- // Computes ConvexHull of input WKT and returns a polygon (unless all input points are colinear).
- // This function does not require its input to be a valid geography. This function does require
- // that the WKT coordinate values are longitude/latitude values, in that order and that a valid
- // geography SRID value is supplied.
- //
- public static SqlGeography ConvexHullGeographyFromText(string inputWKT, int srid)
- {
- SqlGeometry geometry = SqlGeometry.STGeomFromText(new SqlChars(inputWKT), srid);
- SqlGeographyBuilder geographyBuilder = new SqlGeographyBuilder();
- geometry.Populate(new GeometryToPointGeographySink(geographyBuilder));
- return ConvexHullGeography(geographyBuilder.ConstructedGeography);
- }
-
- // Check if an input geometry can represent a valid geography without throwing an exception.
- // This function requires that the geometry be in longitude/latitude coordinates and that
- // those coordinates are in correct order in the geometry instance (i.e. latitude/longitude
- // not longitude/latitude). This function will return false (0) if the input geometry is not
- // in the correct latitude/longitude format, including a valid geography SRID.
- //
- public static bool IsValidGeographyFromGeometry(SqlGeometry geometry)
- {
- if (geometry.IsNull) return false;
-
- try
- {
- SqlGeographyBuilder builder = new SqlGeographyBuilder();
- geometry.Populate(new VacuousGeometryToGeographySink(geometry.STSrid.Value, builder));
- SqlGeography geography = builder.ConstructedGeography;
- return true;
- }
- catch (FormatException)
- {
- // Syntax error
- return false;
- }
- catch (ArgumentException)
- {
- // Semantical (Geometrical) error
- return false;
- }
- }
-
- // Check if an input WKT can represent a valid geography. This function requires that
- // the WTK coordinate values are longitude/latitude values, in that order and that a valid
- // geography SRID value is supplied. This function will not throw an exception even in
- // edge conditions (i.e. longitude/latitude coordinates are reversed to latitude/longitude).
- //
- public static bool IsValidGeographyFromText(string inputWKT, int srid)
- {
- try
- {
- // If parse succeeds then our input is valid
- SqlGeography.STGeomFromText(new SqlChars(inputWKT), srid);
- return true;
- }
- catch (FormatException)
- {
- // Syntax error
- return false;
- }
- catch (ArgumentException)
- {
- // Semantical (Geometrical) error
- return false;
- }
- }
-
- // Convert an input geometry instance to a valid geography instance.
- // This function requires that the WKT coordinate values are longitude/latitude values,
- // in that order and that a valid geography SRID value is supplied.
- //
- public static SqlGeography MakeValidGeographyFromGeometry(SqlGeometry geometry)
- {
- if (geometry.IsNull) return SqlGeography.Null;
- if (geometry.STIsEmpty().Value) return CreateEmptyGeography(geometry.STSrid.Value);
-
- // Extract vertices from our input to be able to compute geography EnvelopeCenter
- SqlGeographyBuilder pointSetBuilder = new SqlGeographyBuilder();
- geometry.Populate(new GeometryToPointGeographySink(pointSetBuilder));
- SqlGeography center;
- try
- {
- center = pointSetBuilder.ConstructedGeography.EnvelopeCenter();
- }
- catch (ArgumentException)
- {
- // Input is larger than a hemisphere.
- return SqlGeography.Null;
- }
-
- // Construct Gnomonic projection centered on input geography
- SqlProjection gnomonicProjection = SqlProjection.Gnomonic(center.Long.Value, center.Lat.Value);
-
- // Project, run geometry MakeValid and unproject
- SqlGeometryBuilder geometryBuilder = new SqlGeometryBuilder();
- geometry.Populate(new VacuousGeometryToGeographySink(geometry.STSrid.Value, new Projector(gnomonicProjection, geometryBuilder)));
- SqlGeometry outGeometry = MakeValidForGeography(geometryBuilder.ConstructedGeometry);
-
- try
- {
- return gnomonicProjection.Unproject(outGeometry);
- }
- catch (ArgumentException)
- {
- // Try iteratively to reduce the object to remove very close vertices.
- for (double tollerance = 1e-4; tollerance <= 1e6; tollerance *= 2)
- {
- try
- {
- return gnomonicProjection.Unproject(outGeometry.Reduce(tollerance));
- }
- catch (ArgumentException)
- {
- // keep trying
- }
- }
- return SqlGeography.Null;
- }
- }
-
- private static SqlGeography CreateEmptyGeography(int srid)
- {
- SqlGeographyBuilder b = new SqlGeographyBuilder();
- b.SetSrid(srid);
- b.BeginGeography(OpenGisGeographyType.GeometryCollection);
- b.EndGeography();
- return b.ConstructedGeography;
- }
-
- private static SqlGeometry MakeValidForGeography(SqlGeometry geometry)
- {
- // Note: This function relies on an undocumented feature of the planar Union and MakeValid
- // that polygon rings in their result will always be oriented using the same rule that
- // is used in geography. But, it is not good practice to rely on such fact in production code.
-
- if (geometry.STIsValid().Value && !geometry.STIsEmpty().Value)
- return geometry.STUnion(geometry.STPointN(1));
-
- return geometry.MakeValid();
- }
-
- // Convert an input WKT to a valid geography instance.
- // This function requires that the WKT coordinate values are longitude/latitude values,
- // in that order and that a valid geography SRID value is supplied.
- //
- public static SqlGeography MakeValidGeographyFromText(string inputWKT, int srid)
- {
- return MakeValidGeographyFromGeometry(SqlGeometry.STGeomFromText(new SqlChars(inputWKT), srid));
- }
-
- // Selectively filter unwanted artifacts in input object:
- // - empty shapes (if [filterEmptyShapes] is true)
- // - points (if [filterPoints] is true)
- // - linestrings shorter than provided tolerance (if lineString.STLength < [lineStringTolerance])
- // - polygon rings thinner than provied tolerance (if ring.STArea < ring.STLength * [ringTolerance])
- // - general behaviour: Returned spatial objects will always to the simplest OGC construction
- //
- public static SqlGeometry FilterArtifactsGeometry(SqlGeometry g, bool filterEmptyShapes, bool filterPoints, double lineStringTolerance, double ringTolerance)
- {
- if (g == null || g.IsNull)
- return g;
-
- SqlGeometryBuilder b = new SqlGeometryBuilder();
- IGeometrySink filter = b;
-
- if (filterEmptyShapes)
- filter = new GeometryEmptyShapeFilter(filter);
- if (ringTolerance > 0)
- filter = new GeometryThinRingFilter(filter, ringTolerance);
- if (lineStringTolerance > 0)
- filter = new GeometryShortLineStringFilter(filter, lineStringTolerance);
- if (filterPoints)
- filter = new GeometryPointFilter(filter);
-
- g.Populate(filter);
- g = b.ConstructedGeometry;
-
- if (g == null || g.IsNull || !g.STIsValid().Value)
- return g;
-
- // Strip collections with single element
- while (g.STNumGeometries().Value == 1 && g.InstanceOf("GEOMETRYCOLLECTION").Value)
- g = g.STGeometryN(1);
-
- return g;
- }
-
- // Selectively filter unwanted artifacts in input object:
- // - empty shapes (if [filterEmptyShapes] is true)
- // - points (if [filterPoints] is true)
- // - linestrings shorter than provided tolerance (if lineString.STLength < [lineStringTolerance])
- // - polygon rings thinner than provied tolerance (if ring.STArea < ring.STLength * [ringTolerance])
- // - general behaviour: Returned spatial objects will always to the simplest OGC construction
- //
- public static SqlGeography FilterArtifactsGeography(SqlGeography g, bool filterEmptyShapes, bool filterPoints, double lineStringTolerance, double ringTolerance)
- {
- if (g == null || g.IsNull)
- return g;
-
- SqlGeographyBuilder b = new SqlGeographyBuilder();
- IGeographySink filter = b;
-
- if (filterEmptyShapes)
- filter = new GeographyEmptyShapeFilter(filter);
- if (ringTolerance > 0)
- filter = new GeographyThinRingFilter(filter, ringTolerance);
- if (lineStringTolerance > 0)
- filter = new GeographyShortLineStringFilter(filter, lineStringTolerance);
- if (filterPoints)
- filter = new GeographyPointFilter(filter);
-
- g.Populate(filter);
- g = b.ConstructedGeography;
-
- if (g == null || g.IsNull)
- return g;
-
- // Strip collections with single element
- while (g.STNumGeometries().Value == 1 && g.InstanceOf("GEOMETRYCOLLECTION").Value)
- g = g.STGeometryN(1);
-
- return g;
- }
- }
-}
\ No newline at end of file
diff --git a/KMLProcessor/Constants.cs b/KMLProcessor/Constants.cs
deleted file mode 100644
index da7130d..0000000
--- a/KMLProcessor/Constants.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- public class Constants
- {
- ///
- /// SQL Server default SRID. SQL Server uses the default SRID of 4326,
- /// which maps to the WGS 84 spatial reference system, when using methods on geography instances.
- ///
- /// Source: http://msdn.microsoft.com/en-us/library/bb964707.aspx
- ///
- public static int DefaultSRID
- {
- get
- {
- return 4326;
- }
- }
-
- ///
- /// Google's KML extensions namespace
- ///
- public static string GxNamespace
- {
- get
- {
- return "http://www.google.com/kml/ext/2.2";
- }
- }
-
- ///
- /// KML namespace
- ///
- public static string KmlNamespace
- {
- get
- {
- return "http://www.opengis.net/kml/2.2";
- }
- }
-
- ///
- /// Atom namespace
- ///
- public static string AtomNamespace
- {
- get
- {
- return "http://www.w3.org/2005/Atom";
- }
- }
- }
-}
diff --git a/KMLProcessor/FilterSetSridGeographySink.cs b/KMLProcessor/FilterSetSridGeographySink.cs
deleted file mode 100644
index 3d163e6..0000000
--- a/KMLProcessor/FilterSetSridGeographySink.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class will propagate all method calls to the given target sink,
- /// except the method call to the SetSrid method.
- ///
- public class FilterSetSridGeographySink : IGeographySink
- {
- #region Constructors
-
- ///
- /// Default Constructor
- ///
- /// Target sink
- public FilterSetSridGeographySink(IGeographySink targetSink)
- {
- m_TargetSink = targetSink;
- }
-
- #endregion
-
- #region Private Data
-
- ///
- /// Target sink to propagate all method calls to it, except the method call to the SetSrid method
- ///
- private IGeographySink m_TargetSink;
-
- #endregion
-
- #region IGeographySink Members
-
- public void AddLine(double latitude, double longitude, double? z, double? m)
- {
- if (m_TargetSink != null)
- m_TargetSink.AddLine(latitude, longitude, z, m);
- }
-
- public void BeginFigure(double latitude, double longitude, double? z, double? m)
- {
- if (m_TargetSink != null)
- m_TargetSink.BeginFigure(latitude, longitude, z, m);
- }
-
- public void BeginGeography(OpenGisGeographyType type)
- {
- if (m_TargetSink != null)
- m_TargetSink.BeginGeography(type);
- }
-
- public void EndFigure()
- {
- if (m_TargetSink != null)
- m_TargetSink.EndFigure();
- }
-
- public void EndGeography()
- {
- if (m_TargetSink != null)
- m_TargetSink.EndGeography();
- }
-
- ///
- /// This method call will not be propagated to the target sink
- ///
- ///
- public void SetSrid(int srid)
- {
- }
-
- #endregion
- }
-}
diff --git a/KMLProcessor/GeographyContext.cs b/KMLProcessor/GeographyContext.cs
deleted file mode 100644
index 0b534a6..0000000
--- a/KMLProcessor/GeographyContext.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class contains the information about the context where a geography instance is found.
- ///
- public class GeographyContext
- {
- ///
- /// Geography instance Id
- ///
- public string Id
- {
- get { return m_Id; }
- set { m_Id = value; }
- }
- ///
- /// Data member for the Id property
- ///
- protected string m_Id = null;
-
- ///
- /// Geography instance context. It contains the information about the parent and
- /// the siblings of this geography instance.
- /// It also contains the information about this geography instance.
- ///
- public string Context
- {
- get { return m_Context; }
- set { m_Context = value; }
- }
- ///
- /// Data member for the Context property
- ///
- protected string m_Context = null;
-
- ///
- /// The extracted geography instance
- ///
- public SqlGeography Shape
- {
- get { return m_Shape; }
- set { m_Shape = value; }
- }
- ///
- /// Data member for the Shape property
- ///
- protected SqlGeography m_Shape = null;
- }
-}
diff --git a/KMLProcessor/Import/AltitudeMode.cs b/KMLProcessor/Import/AltitudeMode.cs
deleted file mode 100644
index 1c778ab..0000000
--- a/KMLProcessor/Import/AltitudeMode.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// Altitude modes allowed in KML
- ///
- public enum AltitudeMode
- {
- clampToGround = 0,
- relativeToGround = 1,
- absolute = 2,
- clampToSeaFloor = 3,
- relativeToSeaFloor = 4
- }
-}
diff --git a/KMLProcessor/Import/Geography.cs b/KMLProcessor/Import/Geography.cs
deleted file mode 100644
index c1d7ccb..0000000
--- a/KMLProcessor/Import/Geography.cs
+++ /dev/null
@@ -1,225 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// Base class for all geography instances extracted from the KML file
- ///
- public abstract class Geography
- {
- #region Public Properties
-
- ///
- /// Id of the placemark which contains this geography instance
- ///
- public string Id
- {
- get { return m_Id; }
- set { m_Id = value; }
- }
- ///
- /// Data member for the Id property
- ///
- protected string m_Id = null;
-
- ///
- /// True if point(s) which are part of this geography instance
- /// should be connected to the ground when they are visualy represented in some visualisation tool
- ///
- public bool Extrude
- {
- get { return m_Extrude; }
- set
- {
- m_Extrude = value;
- }
- }
- ///
- /// Data member for the Extrude property
- ///
- protected bool m_Extrude = false;
-
- ///
- /// True if all lines in this geography instance should follow the terrain.
- /// This flag is not applicable just for Point instance.
- ///
- public bool Tesselate
- {
- get { return m_Tesselate; }
- set
- {
- m_Tesselate = value;
- }
- }
- ///
- /// Data member for the Tesselate property
- ///
- protected bool m_Tesselate = false;
-
- ///
- /// True if this geography instance is valid
- ///
- public bool IsValid
- {
- get
- {
- // This implementation is base on the property that the Sql Server 2008 (10.0 Katmai)
- // will throw an exception if the geography instance is not valid.
-
- try
- {
- SqlGeographyBuilder constructed = new SqlGeographyBuilder();
- constructed.SetSrid(Constants.DefaultSRID);
-
- Populate(constructed);
-
- SqlGeography g = constructed.ConstructedGeography;
-
- return true;
- }
- catch (Exception)
- {
- return false;
- }
- }
- }
-
- ///
- /// The context where this geography instance is found. It will
- /// contain the information about the parent and the siblings of this
- /// geography instance, and the information about this geography instance
- /// it self.
- ///
- public string Context
- {
- get { return m_Context; }
- set { m_Context = value; }
- }
- ///
- /// Data member for the Context property
- ///
- protected string m_Context;
-
- #endregion
-
- #region Abstract Members
-
- ///
- /// This method populates the given sink with the data from this geography instance
- ///
- /// Sink to be populated
- public abstract void Populate(IGeographySink sink);
-
- ///
- /// SqlGeography instance well-known text.
- ///
- public abstract string WKT
- {
- get;
- }
-
- #endregion
-
- #region Public Methods
-
- ///
- /// This method returns the geography instance that corresponds to this KML geography
- ///
- /// If true and this geography instance is invalid then the MakeValid
- /// function will be executed on this geography instance
- /// The geography instance that corresponds to this KML geography
- public SqlGeography ToSqlGeography(bool makeValid)
- {
- if (IsValid)
- {
- SqlGeographyBuilder constructed = new SqlGeographyBuilder();
- constructed.SetSrid(Constants.DefaultSRID);
- Populate(constructed);
- return constructed.ConstructedGeography;
- }
- else if (makeValid)
- {
- SqlGeographyBuilder constructed = new SqlGeographyBuilder();
- constructed.SetSrid(Constants.DefaultSRID);
- MakeValid(constructed);
- return constructed.ConstructedGeography;
- }
-
- throw new KMLException("Invalid geography instance.");
- }
-
- ///
- /// This method returns a string representation of this object
- ///
- /// WKT for this geography instance
- public override string ToString()
- {
- return WKT;
- }
-
- ///
- /// This method populates the given sink with the data from this geography instance.
- /// If this geography instance is invalid and the makeValid flag is set then a valid geography instance
- /// will be constructed and the given sink will be populated with that instance.
- ///
- /// Sink to be populated
- /// If true and this geography instance is invalid then the MakeValid
- /// function will be executed on this geography instance
- public void Populate(
- IGeographySink sink,
- bool makeValid)
- {
- if (makeValid)
- {
- if (IsValid)
- Populate(sink);
- else
- MakeValid(sink);
- }
- else
- {
- Populate(sink);
- }
- }
-
- ///
- /// This method populates the given sink with the valid geography instance constructed from this geography instance.
- ///
- /// Sink to be populated
- public void MakeValid(IGeographySink sink)
- {
- // 1. Creates the valid geography for this WKT
- SqlGeography vg = SQLSpatialTools.Functions.MakeValidGeographyFromText(this.WKT, Constants.DefaultSRID);
-
- // 2. Populates the given sink with the valid geography
- vg.Populate(new FilterSetSridGeographySink(sink));
- }
-
- #endregion
-
- #region Internal Properties
-
- ///
- /// Id which was assigned to this geography instance when it was inserted in the database.
- /// If the value is null then it is not stored in the database yet
- ///
- internal int? DbId
- {
- get { return m_DbId; }
- set
- {
- m_DbId = value;
- }
- }
- ///
- /// Data member for the DbId property
- ///
- private int? m_DbId;
-
- #endregion
- }
-}
-
diff --git a/KMLProcessor/Import/GroundOverlay.cs b/KMLProcessor/Import/GroundOverlay.cs
deleted file mode 100644
index b161deb..0000000
--- a/KMLProcessor/Import/GroundOverlay.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class holds the information about the ground overlay extracted from the KML file
- ///
- public class GroundOverlay : Geography
- {
- #region Public Properties
-
- ///
- /// Ground Overlay Name
- ///
- public string Name
- {
- get { return m_Name; }
- internal set
- {
- if (string.IsNullOrEmpty(value))
- m_Name = "";
- else
- m_Name = value;
- }
- }
- ///
- /// Data member for the Name property
- ///
- protected string m_Name = "";
-
- ///
- /// Box defined inside this ground overlay.
- ///
- public Geography Box
- {
- get { return m_Box; }
- internal set
- {
- m_Box = value;
- }
- }
- ///
- /// Data member for the Box property
- ///
- protected Geography m_Box = null;
-
- ///
- /// Region defined inside this ground overlay.
- ///
- public Region Region
- {
- get { return m_Region; }
- internal set
- {
- m_Region = value;
- }
- }
- ///
- /// Data member for the Region property
- ///
- protected Region m_Region;
-
- ///
- /// SqlGeography instance well-known text.
- ///
- public override string WKT
- {
- get
- {
- if (Box != null)
- {
- return Box.WKT;
- }
- else if (Region != null)
- {
- return Region.WKT;
- }
- else
- throw new KMLException("WKT is not defined.");
- }
- }
-
- #endregion
-
- #region Geography Methods
-
- ///
- /// This method populates the given sink with the data from this geography instance
- ///
- /// Sink to be populated
- public override void Populate(Microsoft.SqlServer.Types.IGeographySink sink)
- {
- if (Box != null)
- {
- Box.Populate(sink);
- }
- else if (Region != null)
- {
- Region.Populate(sink);
- }
- }
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/KMLParser.cs b/KMLProcessor/Import/KMLParser.cs
deleted file mode 100644
index 7926317..0000000
--- a/KMLProcessor/Import/KMLParser.cs
+++ /dev/null
@@ -1,1211 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Xml;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class parses the given KML string
- ///
- public class KMLParser
- {
- #region Constructors
-
- ///
- /// Constructor. Accepts the string in the KML format.
- ///
- /// KML string to be parsed
- public KMLParser(string kml)
- {
- m_Document = new XmlDocument();
- m_Document.LoadXml(kml);
- }
-
- #endregion
-
- #region Public Data
-
- ///
- /// Geography instances extracted from the KML string
- ///
- public IList Geographies
- {
- get
- {
- return m_Geographies;
- }
- }
-
- ///
- /// Placemarks extracted from the KML string
- ///
- public IList Placemarks
- {
- get
- {
- return m_Placemarks;
- }
- }
-
- ///
- /// Models extracted from the KML string
- ///
- public IList Models
- {
- get
- {
- return m_Models;
- }
- }
-
- ///
- /// Regions extracted from the KML string
- ///
- public IList Regions
- {
- get
- {
- return m_Regions;
- }
- }
-
- ///
- /// Ground overlays extracted from the KML string
- ///
- public IList GroundOverlays
- {
- get
- {
- return m_GroundOverlays;
- }
- }
-
- #endregion
-
- #region Protected Data
-
- ///
- /// Xml document whose data will be traversed
- ///
- protected XmlDocument m_Document;
-
- ///
- /// Geography instances extracted from the KML string
- ///
- protected List m_Geographies = new List();
-
- ///
- /// Placemarks extracted from the KML string
- ///
- protected List m_Placemarks = new List();
-
- ///
- /// Models extracted from the KML string
- ///
- protected List m_Models = new List();
-
- ///
- /// Regions extracted from the KML string
- ///
- protected List m_Regions = new List();
-
- ///
- /// Ground overlays extracted from the KML string
- ///
- protected List m_GroundOverlays = new List();
-
- #endregion
-
- #region Protected Methods
-
- ///
- /// This method visits the given KML node
- ///
- /// KML node to be visited
- protected void Visit(XmlNode node)
- {
- if (node == null) return;
-
- string nodeName = node.Name.ToLowerInvariant();
- XmlElement elem = node as XmlElement;
-
- if (elem != null)
- {
- if (nodeName.Equals("point"))
- {
- VisitPoint(elem);
- }
- else if (nodeName.Equals("linestring"))
- {
- VisitLineString(elem);
- }
- else if (nodeName.Equals("linearring"))
- {
- VisitLinearRing(elem);
- }
- else if (nodeName.Equals("polygon"))
- {
- VisitPolygon(elem);
- }
- else if (nodeName.Equals("multigeometry"))
- {
- VisitMultiGeometry(elem);
- }
- else if (nodeName.Equals("placemark"))
- {
- VisitPlacemark(elem);
- }
- else if (nodeName.Equals("model"))
- {
- VisitModel(elem);
- }
- else if (nodeName.Equals("region"))
- {
- VisitRegion(elem);
- }
- else if (nodeName.Equals("groundoverlay"))
- {
- VisitGroundOverlay(elem);
- }
- else
- {
- // Visits the child nodes
- foreach (XmlNode child in node.ChildNodes)
- {
- Visit(child);
- }
- }
- }
- }
-
- ///
- /// This method visits a KML Ground Overlay element
- ///
- /// KML Ground Overlay element
- protected void VisitGroundOverlay(XmlElement element)
- {
- if (element == null) return;
-
- GroundOverlay groundOverlay = new GroundOverlay();
-
- VisitGeography(element, groundOverlay);
- groundOverlay.Name = GetChildElementText(element, "name");
-
- XmlElement latLonAltBoxElem = GetFirstChild(element, "LatLonAltBox");
- XmlElement latLonBoxElem = GetFirstChild(element, "LatLonBox");
- XmlElement latLonQuadElem = GetFirstChild(element, "gx:LatLonQuad");
-
- int numOfGegoraphiesBefore = m_Geographies.Count;
-
- if (latLonAltBoxElem != null)
- {
- VisitLatLonAltBox(latLonAltBoxElem);
- }
- else if (latLonBoxElem != null)
- {
- VisitLatLonBox(latLonBoxElem);
- }
- else if (latLonQuadElem != null)
- {
- VisitLatLonQuad(latLonQuadElem);
- }
-
- int numOfGeographiesAfter = m_Geographies.Count;
-
- if (numOfGeographiesAfter == numOfGegoraphiesBefore + 1)
- {
- groundOverlay.Box = m_Geographies[m_Geographies.Count - 1];
- m_Geographies.RemoveAt(m_Geographies.Count - 1);
- }
- else if (numOfGeographiesAfter > numOfGegoraphiesBefore + 1)
- {
- // Multiple geography instances found in a single ground overlay.
- // The last geography instance is the border.
-
- groundOverlay.Box = m_Geographies[m_Geographies.Count - 1];
- m_Geographies.RemoveAt(m_Geographies.Count - 1);
- }
-
- #region Check if a region is also defined inside this ground overlay
-
- XmlElement regionElem = GetFirstChild(element, "Region");
- if (regionElem != null)
- {
- int numOfRegionsBefore = m_Regions.Count;
-
- VisitRegion(regionElem);
-
- int numOfRegionsAfter = m_Regions.Count;
-
- if (numOfRegionsAfter == numOfRegionsBefore + 1)
- {
- groundOverlay.Region = m_Regions[numOfRegionsAfter - 1];
- }
- else if (numOfRegionsAfter > numOfRegionsBefore + 1)
- {
- // Multiple regions found in a single ground overlay. The last region is the border.
-
- groundOverlay.Region = m_Regions[numOfRegionsAfter - 1];
- }
- }
-
- #endregion
-
- m_GroundOverlays.Add(groundOverlay);
- }
-
- ///
- /// This method visits a KML placemark element
- ///
- /// KML placemark element
- protected void VisitPlacemark(XmlElement elem)
- {
- if (elem == null) return;
-
- Placemark placemark = new Placemark();
-
- placemark.Id = GetAttribute(elem, "id");
-
- // Visits the child nodes
- foreach (XmlNode child in elem.ChildNodes)
- {
- string childName = child.Name.ToLowerInvariant();
- if (childName.Equals("name"))
- {
- if (child.HasChildNodes)
- {
- placemark.Name = child.ChildNodes[0].InnerText;
- }
- }
- else if (childName.Equals("description"))
- {
- if (child.HasChildNodes)
- {
- placemark.Description = child.ChildNodes[0].InnerText;
- }
- }
- else if (childName.Equals("address"))
- {
- if (child.HasChildNodes)
- {
- placemark.Address = child.ChildNodes[0].InnerText;
- }
- }
- else if (childName.Equals("lookat") && (child as XmlElement) != null)
- {
- int numOfGeographiesBefore = m_Geographies.Count;
-
- VisitLookAt(child as XmlElement);
-
- int numOfGeographiesAfter = m_Geographies.Count;
-
- if (numOfGeographiesAfter > numOfGeographiesBefore)
- {
- // Point found where this placemark looks at
-
- placemark.LookAt = m_Geographies[m_Geographies.Count - 1];
- m_Geographies.RemoveAt(m_Geographies.Count - 1);
- }
- }
- else
- {
- int numOfGeographiesBefore = m_Geographies.Count;
-
- Visit(child);
-
- int numOfGeographiesAfter = m_Geographies.Count;
-
- if (numOfGeographiesAfter > numOfGeographiesBefore)
- {
- // Found the geography instance which describes this placemark
-
- placemark.Geography = m_Geographies[m_Geographies.Count - 1];
- }
- }
- }
-
- m_Placemarks.Add(placemark);
- }
-
- ///
- /// This method visits a LookAt element
- ///
- /// KML lookat element to be visited
- protected void VisitLookAt(XmlElement elem)
- {
- if (elem == null) return;
-
- VisitLonLatAltElement(elem);
- }
-
- ///
- /// This method parses the given coordinates string.
- ///
- /// String with coordinates to be parsed
- /// List of points contained in the given string
- protected IList ParseCoordinates(string coordinates)
- {
- List points = new List();
-
- if (string.IsNullOrEmpty(coordinates) || coordinates.Trim().Length == 0)
- return points;
-
- // Removes the spaces inside the touples
- while (coordinates.Contains(", "))
- {
- coordinates = coordinates.Replace(", ", ",");
- }
-
- // Splits the tuples
- string[] coordinate = coordinates.Trim().Split(' ', '\t', '\n');
- if (coordinate == null)
- return points;
-
- // Process each tuple
- foreach (string p in coordinate)
- {
- string tp = p.Trim();
- if (tp.Length == 0)
- continue;
-
- string[] cords = tp.Split(',');
-
- try
- {
- double longitude = double.Parse(cords[0], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture);
- double latitude = double.Parse(cords[1], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture);
- double? altitude = null;
-
- if (cords.Length == 3) // the altitude is optional
- {
- altitude = double.Parse(cords[2], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture);
- }
-
- Point point = new Point();
- point.Longitude = longitude;
- point.Latitude = latitude;
- point.Altitude = altitude;
-
- // If point is the same as the previous point then skip it
- if (points.Count > 0 && points[points.Count - 1].Equals(point))
- continue;
-
- points.Add(point);
- }
- catch (Exception exc)
- {
- throw new KMLException("Coordinate is in wrong format: " + tp, exc);
- }
- }
-
- return points;
- }
-
- ///
- /// This method performs the visit which is common to all geography instances.
- /// It stores extracted information into the given geography object.
- ///
- /// Xml element to be visited
- /// Geography object where data should be stored
- protected void VisitGeography(XmlElement elem, Geography geography)
- {
- #region The id in the KML string
-
- geography.Id = GetAttribute(elem, "id");
-
- #endregion
-
- #region The extrude flag
-
- XmlElement extrudeElem = GetFirstChild(elem, "extrude");
- if (extrudeElem != null && extrudeElem.HasChildNodes)
- {
- string val = extrudeElem.ChildNodes[0].InnerText;
- if (val != null && val.Trim().Equals("1"))
- {
- geography.Extrude = true;
- }
- }
-
- #endregion
-
- #region The tessellate flag
-
- XmlElement tessellateElem = GetFirstChild(elem, "tessellate");
- if (tessellateElem != null && tessellateElem.HasChildNodes)
- {
- string val = tessellateElem.ChildNodes[0].InnerText;
- if (val != null && val.Trim().Equals("1"))
- {
- geography.Tesselate = true;
- }
- }
-
- #endregion
- }
-
- ///
- /// This method visits a KML Point element
- ///
- /// KML Point element to be visited
- protected void VisitPoint(XmlElement elem)
- {
- if (elem == null) return;
-
- // Extracts the point coordinates
- XmlElement coordinates = GetFirstChild(elem, "coordinates");
- if (coordinates == null || !coordinates.HasChildNodes)
- return;
-
- XmlNode coordinatesTextNode = coordinates.ChildNodes[0];
-
- string coordinatesText = coordinatesTextNode.InnerText;
-
- IList pts = ParseCoordinates(coordinatesText);
-
- if (pts.Count == 0)
- return;
-
- Point point = pts[0];
-
- // Extracts the altitude mode
- point.Measure = GetAltitudeModeCode(elem);
-
- // Extracts the other non-geographic data
- VisitGeography(elem, point);
-
- // Sets the geography instance context
- point.Context = (elem.ParentNode != null) ? elem.ParentNode.OuterXml : elem.OuterXml;
-
- m_Geographies.Add(point);
- }
-
- ///
- /// This method visits a KML LineString element
- ///
- /// KML LineString element to be visited
- protected void VisitLineString(XmlElement elem)
- {
- VisitLineString(elem, false);
- }
-
- ///
- /// This method visits a KML LineString element
- ///
- /// KML line string element to be visited
- /// True if the line is the ring
- protected void VisitLineString(
- XmlElement elem,
- bool isLineRing)
- {
- if (elem == null) return;
-
- XmlNode coordinates = GetFirstChild(elem, "coordinates");
- if (coordinates == null || !coordinates.HasChildNodes)
- return;
-
- XmlNode txt = coordinates.ChildNodes[0];
-
- string allCoordinates = txt.InnerText;
-
- IList points = ParseCoordinates(allCoordinates);
- if (points == null || points.Count == 0)
- return;
-
- LineString ls = null;
- if (isLineRing)
- {
- ls = new LinearRing();
- }
- else
- {
- ls = new LineString();
- }
-
- ls.Points.AddRange(points);
-
- int? altitudeModeCode = GetAltitudeModeCode(elem);
-
- foreach (Point p in ls.Points)
- {
- p.Measure = altitudeModeCode;
- }
-
- // Extract other non-geographic data
- VisitGeography(elem, ls);
-
- #region Handle tesselate flag
-
- ls.StoreTesselateFlag();
-
- #endregion
-
- // Sets the geography instance context
- ls.Context = (elem.ParentNode != null) ? elem.ParentNode.OuterXml : elem.OuterXml;
-
- m_Geographies.Add(ls);
- }
-
- ///
- /// This method visits a KML LinearRing element
- ///
- /// KML LinearRing element to be visited
- protected void VisitLinearRing(XmlElement elem)
- {
- VisitLineString(elem, true);
- }
-
- ///
- /// This method visits a KML Polygon element
- ///
- /// KML Polygon element to be visited
- protected void VisitPolygon(XmlElement elem)
- {
- if (elem == null) return;
-
- #region Extract Outer Border
-
- XmlElement outerBoundaryIs = GetFirstChild(elem, "outerBoundaryIs");
- if (outerBoundaryIs == null)
- return;
-
- XmlElement outerLinearRing = GetFirstChild(outerBoundaryIs, "LinearRing");
- if (outerLinearRing == null)
- return;
-
- int numOfGeographiesBefore = this.Geographies.Count;
-
- VisitLinearRing(outerLinearRing);
-
- int numOfGeographiesAfter = this.Geographies.Count;
-
- // Extracts the outer ring
- if (numOfGeographiesAfter == numOfGeographiesBefore)
- throw new KMLException("Outer ring not found!");
-
- int index = this.Geographies.Count - 1;
- Geography g = this.Geographies[index];
- this.m_Geographies.RemoveAt(index);
-
- LinearRing outerRing = g as LinearRing;
- if (outerRing == null)
- throw new KMLException("Outer ring not created!");
-
- Polygon polygon = new Polygon();
-
- bool isValidPolygon = true;
-
- #region Check outer ring orientation. Outer boundary should be oriented counter-clockwise
-
- if (!outerRing.IsValid)
- {
- // Checks if just the ring's orientation is wrong
- outerRing.SwitchOrientation();
-
- if (!outerRing.IsValid)
- {
- isValidPolygon = false;
- outerRing.SwitchOrientation();
- }
- }
-
- #endregion
-
- polygon.OuterRing = outerRing;
-
- #endregion
-
- #region Extract Inner Borders
-
- XmlNodeList innerBoundaryIsLst = elem.GetElementsByTagName("innerBoundaryIs");
- if (innerBoundaryIsLst != null)
- {
- foreach (XmlNode innerBorderNode in innerBoundaryIsLst)
- {
- XmlElement innerBoundaryIs = innerBorderNode as XmlElement;
- if (innerBoundaryIs == null)
- continue;
-
- XmlElement innerLinearRing = GetFirstChild(innerBoundaryIs, "LinearRing");
- if (innerLinearRing == null)
- continue;
-
- numOfGeographiesBefore = this.Geographies.Count;
-
- VisitLinearRing(innerLinearRing);
-
- numOfGeographiesAfter = this.Geographies.Count;
-
- // Extracts the inner ring
- if (numOfGeographiesBefore == numOfGeographiesAfter)
- throw new KMLException("Inner ring not found!");
-
- index = this.Geographies.Count - 1;
- g = this.Geographies[index];
- this.m_Geographies.RemoveAt(index);
-
- LinearRing innerRing = g as LinearRing;
- if (innerRing == null)
- throw new KMLException("Inner ring not created!");
-
- #region Check the ring's orientation. Inner rings should be clockwise oriented
-
- if (isValidPolygon)
- {
- if (innerRing.IsValid)
- {
- // The inner rings should be in the opposite direction from the outer ring
-
- innerRing.SwitchOrientation();
- }
- }
-
- #endregion
-
- polygon.InnerRing.Add(innerRing);
- }
- }
-
- #endregion
-
- // Extracts the other non-geographic data
- VisitGeography(elem, polygon);
-
- #region Extract altitude mode
-
- int? altitudeModeCode = GetAltitudeModeCode(elem);
- if (altitudeModeCode.HasValue)
- {
- // Updates all the points on the outer bound
- if (polygon.OuterRing != null)
- {
- foreach (Point p in polygon.OuterRing.Points)
- {
- p.Measure = altitudeModeCode;
- }
- }
-
- // Update all the points on all the inner bounds
- if (polygon.InnerRing != null)
- {
- foreach (LinearRing r in polygon.InnerRing)
- {
- if (r.Points == null)
- return;
-
- foreach (Point p1 in r.Points)
- {
- p1.Measure = altitudeModeCode;
- }
- }
- }
- }
-
- #endregion
-
- #region Handles tesselate flag
-
- if (polygon.Tesselate)
- {
- if (polygon.OuterRing != null)
- polygon.OuterRing.StoreTesselateFlag();
-
- if (polygon.InnerRing != null)
- {
- foreach (LinearRing r in polygon.InnerRing)
- r.StoreTesselateFlag();
- }
- }
-
- #endregion
-
- // Sets the geography instance context
- polygon.Context = (elem.ParentNode != null) ? elem.ParentNode.OuterXml : elem.OuterXml;
-
- m_Geographies.Add(polygon);
- }
-
- ///
- /// This method visits a KML MultyGeometry element
- ///
- /// KML MultyGeometry element to be visited
- protected void VisitMultiGeometry(XmlElement element)
- {
- if (element == null)
- return;
-
- MultiGeometry mg = new MultiGeometry();
-
- if (element.HasChildNodes)
- {
- foreach (XmlNode child in element.ChildNodes)
- {
- int lastIndexBefore = this.Geographies.Count - 1;
-
- Visit(child);
-
- int lastIndex = this.Geographies.Count - 1;
-
- if (lastIndex > lastIndexBefore)
- {
- Geography childGeography = this.Geographies[lastIndex];
- this.Geographies.RemoveAt(lastIndex);
- mg.Geographies.Add(childGeography);
- }
- }
- }
-
- // Sets the geography instance context
- mg.Context = (element.ParentNode != null) ? element.ParentNode.OuterXml : element.OuterXml;
-
- this.Geographies.Add(mg);
- }
-
- ///
- /// This method visits a KML Region element
- ///
- /// KML Region element to be visited
- protected void VisitRegion(XmlElement element)
- {
- if (element == null) return;
-
- Region r = new Region();
-
- VisitGeography(element, r);
-
- XmlElement latLonAltBoxElem = GetFirstChild(element, "LatLonAltBox");
- XmlElement latLonBoxElem = GetFirstChild(element, "LatLonBox");
- XmlElement latLonQuadElem = GetFirstChild(element, "gx:LatLonQuad");
-
- int numOfGegoraphiesBefore = m_Geographies.Count;
-
- if (latLonAltBoxElem != null)
- {
- VisitLatLonAltBox(latLonAltBoxElem);
- }
- else if (latLonBoxElem != null)
- {
- VisitLatLonBox(latLonBoxElem);
- }
- else if (latLonQuadElem != null)
- {
- VisitLatLonQuad(latLonQuadElem);
- }
-
- int numOfGeographiesAfter = m_Geographies.Count;
-
- if (numOfGeographiesAfter > numOfGegoraphiesBefore)
- {
- // Found region's box
-
- r.Box = m_Geographies[m_Geographies.Count - 1];
- m_Geographies.RemoveAt(m_Geographies.Count - 1);
- }
-
- m_Regions.Add(r);
- }
-
- ///
- /// This method visits a KML LatLonAltBox element
- ///
- /// KML LatLonAltBox element to be visited
- protected void VisitLatLonAltBox(XmlElement element)
- {
- if (element == null) return;
-
- #region Required properties
-
- double north = GetChildElementText(element, "north");
- double south = GetChildElementText(element, "south");
- double west = GetChildElementText(element, "west");
- double east = GetChildElementText(element, "east");
-
- #endregion
-
- #region Optional properties
-
- double minAltitude = 0;
- double maxAltitude = 0;
- AltitudeMode? altitudeMode;
- string minAltitudeS = GetChildElementText(element, "minAltitude");
- if (!string.IsNullOrEmpty(minAltitudeS))
- {
- minAltitude = double.Parse(minAltitudeS);
- }
-
- string maxAltitudeS = GetChildElementText(element, "maxAltitude");
- if (!string.IsNullOrEmpty(maxAltitudeS))
- {
- maxAltitude = double.Parse(maxAltitudeS);
- }
-
- altitudeMode = GetAltitudeMode(element);
-
- #endregion
-
- LatLonAltBox b = new LatLonAltBox();
- b.North = north;
- b.South = south;
- b.West = west;
- b.East = east;
- b.MinAltitude = minAltitude;
- b.MaxAltitude = maxAltitude;
- b.AltitudeMode = altitudeMode;
-
- m_Geographies.Add(b);
- }
-
- ///
- /// This method visits a KML LatLonBox element
- ///
- /// KML LatLonBox element to be visited
- protected void VisitLatLonBox(XmlElement element)
- {
- if (element == null) return;
-
- #region Required properties
-
- double north = GetChildElementText(element, "north");
- double south = GetChildElementText(element, "south");
- double west = GetChildElementText(element, "west");
- double east = GetChildElementText(element, "east");
-
- #endregion
-
- #region Optional properties
-
- double rotation = 0;
-
- string rotationS = GetChildElementText(element, "rotation");
- if (!string.IsNullOrEmpty(rotationS))
- {
- rotation = double.Parse(rotationS);
- }
-
- #endregion
-
- LatLonBox b = new LatLonBox();
- b.North = north;
- b.South = south;
- b.West = west;
- b.East = east;
- b.Rotation = rotation;
-
- m_Geographies.Add(b);
- }
-
- ///
- /// This method visits a KML LatLonQuad element
- ///
- /// LatLonQuad KML element to be visited
- protected void VisitLatLonQuad(XmlElement element)
- {
- if (element == null) return;
-
- string coordinates = GetChildElementText(element, "coordinates");
-
- IList points = ParseCoordinates(coordinates);
- if (points == null || points.Count != 4)
- throw new KMLException("In KML element LatLonQuad has to have exactly the 4 coordinates.");
-
- // Closes the polygon. Stores the last point the same as the first one.
- points.Add(points[0].Clone());
-
- LatLonQuad llq = new LatLonQuad();
-
- llq.Points.Clear();
- llq.Points.AddRange(points);
-
- m_Geographies.Add(llq);
- }
-
- ///
- /// This method visits a KML element. Method extracts the child elements:
- /// Longitude, Latitude, Altitude and AltitudeMode and creates the point instance from those values.
- ///
- /// KML element to be visited
- protected void VisitLonLatAltElement(XmlElement element)
- {
- double longitude = 0, latitude = 0, altitude = 0;
- int? altitudeMode = null;
- try
- {
- XmlElement longitudeElem = GetFirstChild(element, "longitude");
- if (longitudeElem != null && longitudeElem.HasChildNodes)
- {
- string s = longitudeElem.ChildNodes[0].InnerText;
- longitude = double.Parse(s);
- }
- XmlElement latitudeElem = GetFirstChild(element, "latitude");
- if (latitudeElem != null && latitudeElem.HasChildNodes)
- {
- string s = latitudeElem.ChildNodes[0].InnerText;
- latitude = double.Parse(s);
- }
- XmlElement altitudeElem = GetFirstChild(element, "altitude");
- if (altitudeElem != null && altitudeElem.HasChildNodes)
- {
- string s = altitudeElem.ChildNodes[0].InnerText;
- altitude = double.Parse(s);
- }
-
- altitudeMode = GetAltitudeModeCode(element);
- }
- catch (Exception exc)
- {
- throw new KMLException("Location coordinates in wrong format!", exc);
- }
-
- Point pt = new Point(longitude, latitude, altitude, altitudeMode);
- m_Geographies.Add(pt);
- }
-
- ///
- /// This method visits a KML Model element
- ///
- /// KML model element to be visited
- protected void VisitModel(XmlElement element)
- {
- if (element == null) return;
-
- Model m = new Model();
-
- #region Extract Location
-
- XmlElement locationElem = GetFirstChild(element, "location");
- if (locationElem != null)
- {
- VisitLonLatAltElement(locationElem);
-
- m.Location = m_Geographies[m_Geographies.Count - 1] as Point;
- m_Geographies.RemoveAt(m_Geographies.Count - 1);
- }
-
- #endregion
-
- #region Extract other non-geographic data
-
- VisitGeography(element, m);
-
- XmlElement orientationElem = GetFirstChild(element, "orientation");
- if (orientationElem != null)
- {
- m.OrientationHeading = GetChildElementText(orientationElem, "heading", 0);
- m.OrientationTilt = GetChildElementText(orientationElem, "tilt", 0);
- m.OrientationRoll = GetChildElementText(orientationElem, "roll", 0);
- }
-
- XmlElement scaleElem = GetFirstChild(element, "scale");
- if (scaleElem != null)
- {
- m.ScaleX = GetChildElementText(scaleElem, "x", 1);
- m.ScaleY = GetChildElementText(scaleElem, "y", 1);
- m.ScaleZ = GetChildElementText(scaleElem, "z", 1);
- }
-
- #endregion
-
- m_Models.Add(m);
- }
-
- #endregion
-
- #region Public methods
-
- ///
- /// This method parses the given KML string
- ///
- public void Parse()
- {
- // Visits the all child nodes
- foreach (XmlNode n in m_Document.ChildNodes)
- {
- Visit(n);
- }
- }
-
- #endregion
-
- #region Public Static Methods
-
- ///
- /// This method parses the given XML file context
- ///
- /// File full path
- /// KMLParser with loaded file context
- public static KMLParser ParseFile(string fileFullPathName)
- {
- string text = System.IO.File.ReadAllText(fileFullPathName);
-
- KMLParser p = new KMLParser(text);
-
- return p;
- }
-
- ///
- /// This method returns the first child element with the given name in the given parent element
- ///
- /// Parent element
- /// Child element name
- /// Child element if it exists, null othervise
- public static XmlElement GetFirstChild(XmlElement element, string childElementName)
- {
- if (element == null || element.HasChildNodes == false || string.IsNullOrEmpty(childElementName))
- return null;
-
- childElementName = childElementName.ToLowerInvariant();
-
- foreach (XmlNode node in element.ChildNodes)
- {
- XmlElement c = node as XmlElement;
- if (c == null)
- continue;
-
- if (c.Name.ToLowerInvariant().Equals(childElementName))
- return c;
- }
-
- return null;
- }
-
- ///
- /// This method determines the altitude mode for the given KML geograpy element.
- ///
- /// KML geography element whose altitude mode should be found
- /// Altitude mode, or null if the altitude mode is not found
- private static AltitudeMode? GetAltitudeMode(XmlElement elem)
- {
- string altitudeModeVal = "clamptoground";
- XmlElement altitudeMode = GetFirstChild(elem, "altitudeMode");
- if (altitudeMode == null)
- altitudeMode = GetFirstChild(elem, "gx:altitudeMode");
-
- if (altitudeMode == null)
- return null;
-
- if (altitudeMode != null && altitudeMode.HasChildNodes && altitudeMode.ChildNodes[0].Value != null)
- {
- altitudeModeVal = altitudeMode.ChildNodes[0].Value.Trim().ToLowerInvariant();
- }
-
- if (altitudeModeVal.Equals("clamptoground"))
- return AltitudeMode.clampToGround;
- else if (altitudeModeVal.Equals("relativetoground"))
- return AltitudeMode.relativeToGround;
- else if (altitudeModeVal.Equals("absolute"))
- return AltitudeMode.absolute;
- else if (altitudeModeVal.Equals("relativetoseafloor"))
- return AltitudeMode.relativeToSeaFloor;
- else if (altitudeModeVal.Equals("clamptoseafloor"))
- return AltitudeMode.clampToSeaFloor;
-
- throw new KMLException("Altitude mode not supported: " + altitudeModeVal);
- }
-
- ///
- /// This method determines the altitude mode code for the given KML element.
- ///
- /// KML element
- /// Altitude mode code, or null if the altitude mode is not found
- public static int? GetAltitudeModeCode(XmlElement elem)
- {
- AltitudeMode? altitideMode = GetAltitudeMode(elem);
-
- if (altitideMode == null)
- return null;
-
- return (int)altitideMode;
- }
-
- ///
- /// This method returns the tesselate flag value for the given KML element.
- ///
- /// KML element
- /// Tesselate flag value
- public static bool GetTesselateFlag(XmlElement elem)
- {
- bool tessalateFlag = false;
- XmlNode tessellate = GetFirstChild(elem, "tessellate");
- if (tessellate != null && tessellate.HasChildNodes)
- {
- XmlNode tessTxt = tessellate.ChildNodes[0];
- if (tessTxt.InnerText != null && tessTxt.InnerText.Trim().Equals("1"))
- tessalateFlag = true;
- }
-
- return tessalateFlag;
- }
-
- ///
- /// This method returns the attribute value of the given xml element.
- ///
- /// Xml element whose attribute is requested
- /// Attribute name
- /// Attribute value, or null value if the attribute is not set
- public static string GetAttribute(XmlElement elem, string attributeName)
- {
- XmlAttribute attribute = elem.Attributes[attributeName];
- if (attribute == null)
- return null;
-
- return attribute.Value;
- }
-
- ///
- /// This method returns the inner text in the child element of the given parent element
- ///
- /// Parent element
- /// Child element name
- /// Child element inner text
- public static string GetChildElementText(XmlElement element, string childElementName)
- {
- if (element == null || !element.HasChildNodes) return null;
-
- XmlElement child = GetFirstChild(element, childElementName);
- if (child == null) return null;
-
- return child.InnerText;
- }
-
- ///
- /// This method returns the inner text in the child element of the given parent element.
- /// The inner text value is converted to the given type T
- ///
- /// Type to convert the inner text value into
- /// Parent element
- /// Child element name
- /// Child text value converted into the type T
- public static T GetChildElementText(XmlElement element, string childElementName)
- {
- string childInnerText = GetChildElementText(element, childElementName);
-
- if (string.IsNullOrEmpty(childInnerText))
- return default(T);
-
- return (T)Convert.ChangeType(childInnerText, typeof(T));
- }
-
- ///
- /// This method returns the inner text in the child element of the given parent element.
- /// The inner text value is converted to the given type T. If the child with the given name doesn't exist then
- /// the given default value will be returned.
- ///
- /// Type to convert the inner text value into
- /// Parent element
- /// Child element name
- /// In case that the child doesn't exist, this value will be returned
- /// Child text value converted into the type T, or the default value if the child is not found
- public static T GetChildElementText(XmlElement element, string childElementName, T defaultValue)
- {
- string childInnerText = GetChildElementText(element, childElementName);
-
- if (string.IsNullOrEmpty(childInnerText))
- return defaultValue;
-
- return (T)Convert.ChangeType(childInnerText, typeof(T));
- }
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/KMLProcessor.cs b/KMLProcessor/Import/KMLProcessor.cs
deleted file mode 100644
index dd5183a..0000000
--- a/KMLProcessor/Import/KMLProcessor.cs
+++ /dev/null
@@ -1,163 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class processes the given KML string and produces the equivalent SqlGeography instance
- /// using sinks.
- ///
- public class KMLProcessor
- {
- #region Constructors
-
- ///
- /// Constructor. Accepts the KML string.
- ///
- /// KML data to be parsed
- public KMLProcessor(string kml)
- {
- m_Kml = kml;
-
- m_Parser = new KMLParser(kml);
- m_Parser.Parse();
- }
-
- #endregion
-
- #region Public Properties
-
- ///
- /// Spatial reference identifier (SRID).
- ///
- public int Srid
- {
- get
- {
- return m_Srid;
- }
- set
- {
- m_Srid = value;
- }
- }
- ///
- /// Data member for the Srid property
- ///
- protected int m_Srid = Constants.DefaultSRID;
-
- ///
- /// Placemarks extracted from the given KML string
- ///
- public IList Placemarks
- {
- get
- {
- return m_Parser.Placemarks;
- }
- }
-
- ///
- /// Models extracted from the given KML string
- ///
- public IList Models
- {
- get
- {
- return m_Parser.Models;
- }
- }
-
- ///
- /// Regions extracted from the given KML string
- ///
- public IList Regions
- {
- get
- {
- return m_Parser.Regions;
- }
- }
-
- ///
- /// Geography instances extracted from the given KML string
- ///
- public IList Geographies
- {
- get
- {
- return m_Parser.Geographies;
- }
- }
-
- ///
- /// Ground overlays extracted from the given KML string
- ///
- public IList GroundOverlays
- {
- get
- {
- return m_Parser.GroundOverlays;
- }
- }
-
- #endregion
-
- #region Public Methods
-
- ///
- /// This method populates the given sink with the information about the geography instance
- /// extracted from the KML string
- ///
- /// Sink to be filled
- /// If true and the extracted geography instance is invalid then the MakeValid
- /// function will be executed on the extracted geography instance
- public void Populate(
- IGeographySink sink,
- bool makeValid)
- {
- sink.SetSrid(m_Srid);
-
- int numOfGeographies = m_Parser.Geographies.Count;
- if (numOfGeographies == 1)
- {
- m_Parser.Geographies[0].Populate(sink, makeValid);
- }
- else if (numOfGeographies > 1)
- {
- sink.BeginGeography(OpenGisGeographyType.GeometryCollection);
-
- foreach (Geography g in m_Parser.Geographies)
- {
- g.Populate(sink, makeValid);
- }
-
- sink.EndGeography();
- }
- else
- {
- // Geography instance is not found. The empty geography collection will be generated.
- sink.BeginGeography(OpenGisGeographyType.GeometryCollection);
- sink.EndGeography();
- }
- }
-
- #endregion
-
- #region Protected Data
-
- ///
- /// KML/XML parser
- ///
- protected KMLParser m_Parser;
-
- ///
- /// KML string to be parsed
- ///
- protected string m_Kml;
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/LatLonAltBox.cs b/KMLProcessor/Import/LatLonAltBox.cs
deleted file mode 100644
index 9102d84..0000000
--- a/KMLProcessor/Import/LatLonAltBox.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class contains the information about the LatLonAltBox element extracted from KML
- ///
- public class LatLonAltBox : LatLonBoxBase
- {
- #region Public Data
-
- ///
- /// The minimal altitude
- ///
- public double MinAltitude
- {
- get { return m_MinAltitude; }
- set
- {
- if (value < 0)
- throw new KMLException("Altitude has to be greater than 0");
-
- m_MinAltitude = value;
-
- if (m_MaxAltitude < m_MinAltitude)
- m_MaxAltitude = m_MinAltitude;
- }
- }
- ///
- /// Data member for the MinAltitude property
- ///
- protected double m_MinAltitude = 0;
-
- ///
- /// The maximal altitude
- ///
- public double MaxAltitude
- {
- get { return m_MaxAltitude; }
- set
- {
- if (value < 0)
- throw new KMLException("Altitude has to be greater than 0");
-
- m_MaxAltitude = value;
- if (m_MinAltitude > m_MaxAltitude)
- m_MinAltitude = m_MaxAltitude;
- }
- }
- ///
- /// Data member for the property MaxAltitude
- ///
- protected double m_MaxAltitude = 0;
-
- ///
- /// Altitude mode
- ///
- public AltitudeMode? AltitudeMode
- {
- get { return m_AltitudeMode; }
- set { m_AltitudeMode = value; }
- }
- ///
- /// Data member for the AltitudeMode property
- ///
- protected AltitudeMode? m_AltitudeMode = null;
-
- #endregion
-
- #region Geography Methods
-
- ///
- /// This method populates the given sink with the data from this geography instance
- ///
- /// Sink to be populated
- public override void Populate(Microsoft.SqlServer.Types.IGeographySink sink)
- {
- // Initializes the altitude to the maximal value
- m_Altitude = MaxAltitude;
-
- // Converts and stores the altitude mode to the spatial m coordinate
- if (AltitudeMode != null)
- m_Measure = (int)AltitudeMode.Value;
-
- base.Populate(sink);
- }
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/LatLonBox.cs b/KMLProcessor/Import/LatLonBox.cs
deleted file mode 100644
index 52c600e..0000000
--- a/KMLProcessor/Import/LatLonBox.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class holds the information about the LatLonBox instance extracted from the KML file
- ///
- public class LatLonBox : LatLonBoxBase
- {
- #region Public Data
-
- ///
- /// The rotation angle.
- ///
- public double Rotation
- {
- get { return m_Rotation; }
- set
- {
- m_Rotation = value;
- }
- }
- ///
- /// Data member for the property Rotation
- ///
- protected double m_Rotation = 0;
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/LatLonBoxBase.cs b/KMLProcessor/Import/LatLonBoxBase.cs
deleted file mode 100644
index aee6c78..0000000
--- a/KMLProcessor/Import/LatLonBoxBase.cs
+++ /dev/null
@@ -1,217 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// Base class for the all KML boxes. All KML boxes will be converted to polygon instances.
- ///
- public class LatLonBoxBase : Geography
- {
- #region Public data
-
- ///
- /// The north side latitude
- ///
- public double North
- {
- get { return m_North; }
- set
- {
- m_North = value;
- }
- }
- ///
- /// Data member for the North property
- ///
- protected double m_North = 0;
-
- ///
- /// The south side latitude
- ///
- public double South
- {
- get { return m_South; }
- set
- {
- m_South = value;
- }
- }
- ///
- /// Data member for the South property
- ///
- protected double m_South = 0;
-
- ///
- /// The east side longitude
- ///
- public double East
- {
- get { return m_East; }
- set
- {
- m_East = value;
- }
- }
- ///
- /// Data member for the East property
- ///
- protected double m_East = 0;
-
- ///
- /// The west side longitude
- ///
- public double West
- {
- get { return m_West; }
- set
- {
- m_West = value;
- }
- }
- ///
- /// Data member for the West property
- ///
- protected double m_West = 0;
-
- ///
- /// Altitude
- ///
- public double? Altitude
- {
- get { return m_Altitude; }
- set
- {
- m_Altitude = value;
- }
- }
- ///
- /// Data member for the Altitude property
- ///
- protected double? m_Altitude = null;
-
- ///
- /// Measure
- ///
- public double? Measure
- {
- get { return m_Measure; }
- set
- {
- m_Measure = value;
- }
- }
- ///
- /// Data member for the Measure property
- ///
- protected double? m_Measure = null;
-
- ///
- /// SqlGeography instance well-known text.
- ///
- public override string WKT
- {
- get
- {
- CheckCoordinates();
-
- string wkt = "polygon((";
-
- // (west, south)
- wkt += new Point(West, South, m_Altitude, m_Measure).Coordinate;
-
- // (east, south)
- wkt += "," + new Point(East, South, m_Altitude, m_Measure).Coordinate;
-
- // (east, notrh)
- wkt += "," + new Point(East, North, m_Altitude, m_Measure).Coordinate;
-
- // (west, north)
- wkt += "," + new Point(West, North, m_Altitude, m_Measure).Coordinate;
-
- // (west, south)
- wkt += "," + new Point(West, South, m_Altitude, m_Measure).Coordinate;
-
- wkt += "))";
- return wkt;
- }
- }
-
- #endregion
-
- #region Geography Methods
-
- ///
- /// This method populates the given sink with the data from this geography instance
- ///
- /// Sink to be populated
- public override void Populate(Microsoft.SqlServer.Types.IGeographySink sink)
- {
- CheckCoordinates();
-
- // The coordinates for this geography instance will be:
- // (west, south), (east, south), (east, notrh), (west, north), (west, south)
- sink.BeginGeography(OpenGisGeographyType.Polygon);
-
- // (west, south)
- sink.BeginFigure(South, West, m_Altitude, m_Measure);
-
- // (east, south)
- sink.AddLine(South, East, m_Altitude, m_Measure);
-
- // (east, notrh)
- sink.AddLine(North, East, m_Altitude, m_Measure);
-
- // (west, north)
- sink.AddLine(North, West, m_Altitude, m_Measure);
-
- // (west, south)
- sink.AddLine(South, West, m_Altitude, m_Measure);
-
- sink.EndFigure();
-
- sink.EndGeography();
- }
-
- #endregion
-
- #region Protected Methods
-
- ///
- /// This method checks if the south latitude is less then the north latitude.
- /// If not then the south and the north will be swapped.
- /// This method also checks if the west longitude if less then the east longitude.
- /// If not then the east and the west will be swapped.
- ///
- protected void CheckCoordinates()
- {
- // Checks if the South and the North latitude are in the range (-90, 90)
- South = Utilities.ShiftInRange(South, 90);
- North = Utilities.ShiftInRange(North, 90);
-
- // Checks if the West and the East longitude are in the range (-180, 180)
- West = Utilities.ShiftInRange(West, 180);
- East = Utilities.ShiftInRange(East, 180);
-
- // Ensures that the south is less then the north
- if (South > North)
- {
- double tmp = South;
- South = North;
- North = tmp;
- }
-
- // Ensures that the west is less then the east
- if (West > East)
- {
- double tmp = West;
- West = East;
- East = tmp;
- }
- }
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/LatLonQuad.cs b/KMLProcessor/Import/LatLonQuad.cs
deleted file mode 100644
index 6a4beeb..0000000
--- a/KMLProcessor/Import/LatLonQuad.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class holds the information about a LatLonQuad instance extracted from the KML file
- ///
- public class LatLonQuad : Geography
- {
- #region Public Data
-
- ///
- /// The quad vertices
- ///
- public List Points
- {
- get { return m_Points; }
- }
- ///
- /// Data member for the Points property
- ///
- protected List m_Points = new List();
-
- ///
- /// SqlGeography instance well-known text.
- ///
- public override string WKT
- {
- get
- {
- if (Points == null || Points.Count == 0)
- throw new KMLException("WKT is not defined.");
-
- string wkt = "polygon((";
-
- bool first = true;
- foreach (Point p in Points)
- {
- if (first)
- first = false;
- else
- wkt += ", ";
-
- wkt += p.WKT;
- }
-
- wkt += "))";
- return wkt;
- }
- }
-
- #endregion
-
- #region Geography methods
-
- ///
- /// This method populates the given sink with the data from this geography instance
- ///
- /// Sink to be populated
- public override void Populate(Microsoft.SqlServer.Types.IGeographySink sink)
- {
- if (Points == null || Points.Count == 0)
- return;
-
- sink.BeginGeography(OpenGisGeographyType.Polygon);
-
- sink.BeginFigure(Points[0].Latitude, Points[0].Longitude, Points[0].Altitude, Points[0].Measure);
-
- for (int i = 1; i < Points.Count; i++)
- {
- sink.AddLine(Points[i].Latitude, Points[i].Longitude, Points[i].Altitude, Points[i].Measure);
- }
-
- sink.EndFigure();
-
- sink.EndGeography();
- }
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/LineString.cs b/KMLProcessor/Import/LineString.cs
deleted file mode 100644
index cb0ba56..0000000
--- a/KMLProcessor/Import/LineString.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class contains the information about a line string extracted from the KML file
- ///
- public class LineString : Geography
- {
- #region Private Fields
-
- ///
- /// OpenGisGeographyType for this geography instance
- ///
- protected OpenGisGeographyType m_OpenGisGeographyType = OpenGisGeographyType.LineString;
-
- #endregion
-
- #region Public Properties
-
- ///
- /// The list of points
- ///
- public List Points
- {
- get { return m_Points; }
- }
- ///
- /// Data member for the Points property
- ///
- public List m_Points = new List();
-
- ///
- /// SqlGeography instance well-known text.
- ///
- public override string WKT
- {
- get
- {
- return "LINESTRING" + Vertices;
- }
- }
-
- ///
- /// Returns the vertices of this linear ring. The returned string will be in the following format:
- /// (V0, V1, V2,..., Vn, V0). Vi will be in the format: longitude latitude [altitude [measure]]
- ///
- public string Vertices
- {
- get
- {
- string result = "(";
- bool isFirst = true;
- foreach (Point p in Points)
- {
- if (!isFirst)
- result += ", ";
- else
- isFirst = false;
-
- result += p.Longitude + " " + p.Latitude;
- if (p.Altitude.HasValue)
- {
- result += " " + p.Altitude.Value;
-
- if (p.Measure.HasValue)
- result += " " + p.Measure.Value;
- }
- }
- result += ")";
-
- return result;
- }
- }
-
- #endregion
-
- #region Geography Methods
-
- ///
- /// This method populates the given sink with the data from this geography instance
- ///
- /// Sink to be populated
- public override void Populate(IGeographySink sink)
- {
- if (Points == null || Points.Count == 0)
- return;
-
- sink.BeginGeography(m_OpenGisGeographyType);
- sink.BeginFigure(Points[0].Latitude, Points[0].Longitude, Points[0].Altitude, Points[0].Measure);
-
- for (int i = 1; i < Points.Count; i++)
- {
- sink.AddLine(Points[i].Latitude, Points[i].Longitude, Points[i].Altitude, Points[i].Measure);
- }
-
- sink.EndFigure();
- sink.EndGeography();
- }
-
- #endregion
-
- #region Public Methods
-
- ///
- /// This method stores the tesellate flag. M coordinates of all the point will be set
- /// to the "clamp to ground" value.
- ///
- public void StoreTesselateFlag()
- {
- if (Tesselate)
- {
- foreach (Point p in Points)
- {
- if (!p.Measure.HasValue)
- {
- p.Measure = (int)AltitudeMode.clampToGround;
- }
- }
- }
- }
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/LinearRing.cs b/KMLProcessor/Import/LinearRing.cs
deleted file mode 100644
index 79522f9..0000000
--- a/KMLProcessor/Import/LinearRing.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-using System.Data.SqlTypes;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class contains the data about a linear ring extracted from the KML file
- ///
- public class LinearRing : LineString
- {
- #region Constructors
-
- ///
- /// Default constructor
- ///
- public LinearRing()
- {
- m_OpenGisGeographyType = OpenGisGeographyType.Polygon;
- }
-
- #endregion
-
- #region Public Mehods
-
- ///
- /// This method switchs the ring's orientation
- ///
- public void SwitchOrientation()
- {
- int i = 1;
- int j = Points.Count - 2; // index of the element before last
- while (i < j)
- {
- Point tmp = m_Points[i];
- m_Points[i] = m_Points[j];
- m_Points[j] = tmp;
-
- i++;
- j--;
- }
- }
-
- #endregion
-
- #region Public Properties
-
- ///
- /// SqlGeography instance well-known text.
- ///
- public override string WKT
- {
- get
- {
- return "POLYGON(" + Vertices + ")";
- }
- }
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/Model.cs b/KMLProcessor/Import/Model.cs
deleted file mode 100644
index 463e71c..0000000
--- a/KMLProcessor/Import/Model.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class holds the information about a KML Model element
- ///
- public class Model : Geography
- {
- #region Public Data
-
- ///
- /// The location of the model
- ///
- public Point Location
- {
- get { return m_Location; }
- set
- {
- m_Location = value;
- }
- }
- ///
- /// Data member for the Location property
- ///
- protected Point m_Location = null;
-
- ///
- /// The orientation heading
- ///
- public double OrientationHeading
- {
- get { return m_OrientationHeading; }
- set { m_OrientationHeading = value; }
- }
- ///
- /// Data member for the OrientationHeading property
- ///
- protected double m_OrientationHeading;
-
- ///
- /// The orientation tilt
- ///
- public double OrientationTilt
- {
- get { return m_OrientationTilt; }
- set { m_OrientationTilt = value; }
- }
- ///
- /// Data member for the OrientationTilt property
- ///
- protected double m_OrientationTilt;
-
- ///
- /// The orientation roll
- ///
- public double OrientationRoll
- {
- get { return m_OrientationRoll; }
- set { m_OrientationRoll = value; }
- }
- ///
- /// Data member for the OrientationRoll property
- ///
- protected double m_OrientationRoll;
-
- ///
- /// The x-axis scale factor
- ///
- public double ScaleX
- {
- get { return m_ScaleX; }
- set { m_ScaleX = value; }
- }
- ///
- /// Data member for the ScaleX property
- ///
- protected double m_ScaleX;
-
- ///
- /// The y-axis scale factor
- ///
- public double ScaleY
- {
- get { return m_ScaleY; }
- set { m_ScaleY = value; }
- }
- ///
- /// Data member for the ScaleY property
- ///
- protected double m_ScaleY;
-
- ///
- /// The z-axis scale factor
- ///
- public double ScaleZ
- {
- get { return m_ScaleZ; }
- set { m_ScaleZ = value; }
- }
- ///
- /// Data member for the ScaleZ property
- ///
- protected double m_ScaleZ;
-
- ///
- /// SqlGeography instance well-known text.
- ///
- public override string WKT
- {
- get
- {
- if (Location != null)
- return Location.WKT;
-
- throw new KMLException("WKT is not defined.");
- }
- }
-
- #endregion
-
- #region Geography Methods
-
- ///
- /// This method populates the given sink with the data from this geography instance
- ///
- /// Sink to be populated
- public override void Populate(Microsoft.SqlServer.Types.IGeographySink sink)
- {
- if (Location != null)
- {
- Location.Populate(sink);
- }
- }
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/MultiGeometry.cs b/KMLProcessor/Import/MultiGeometry.cs
deleted file mode 100644
index eff470a..0000000
--- a/KMLProcessor/Import/MultiGeometry.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class holds the information about a collection of the KML geographies
- ///
- public class MultiGeometry : Geography
- {
- #region Public Properties
-
- ///
- /// Geographies contained in this geography instance
- ///
- public IList Geographies
- {
- get
- {
- return m_Geographies;
- }
- }
- ///
- /// Data member for the Geographies property
- ///
- protected List m_Geographies = new List();
-
- ///
- /// SqlGeography instance well-known text.
- ///
- public override string WKT
- {
- get
- {
- string wkt = "GEOMETRYCOLLECTION(";
-
- bool firstGeography = true;
- foreach (Geography g in m_Geographies)
- {
- if (firstGeography)
- firstGeography = false;
- else
- wkt += ", ";
-
- wkt += g.WKT;
- }
-
- wkt += ")";
- return wkt;
- }
- }
-
- #endregion
-
- #region Geography Methods
-
- ///
- /// This method populates the given sink with the information about this multy geometry instance
- ///
- /// Sink to be populated
- public override void Populate(Microsoft.SqlServer.Types.IGeographySink sink)
- {
- sink.BeginGeography(OpenGisGeographyType.GeometryCollection);
-
- if (this.Geographies != null && this.Geographies.Count > 0)
- {
- foreach (Geography g in this.Geographies)
- {
- g.Populate(sink);
- }
- }
-
- sink.EndGeography();
- }
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/Placemark.cs b/KMLProcessor/Import/Placemark.cs
deleted file mode 100644
index d8c5b41..0000000
--- a/KMLProcessor/Import/Placemark.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class contains the information about a placemark extracted from the KML file
- ///
- public class Placemark
- {
- #region Public Properties
-
- ///
- /// Identifier
- ///
- public string Id
- {
- get { return m_Id; }
- set { m_Id = value; }
- }
- ///
- /// Data member for the Id property
- ///
- protected string m_Id = null;
-
- ///
- /// Name
- ///
- public string Name
- {
- get { return m_Name; }
- set
- {
- if (string.IsNullOrEmpty(value))
- {
- m_Name = "";
- }
- else
- {
- m_Name = value;
- }
- }
- }
- ///
- /// Data member for the property Name
- ///
- protected string m_Name = "";
-
- ///
- /// Description
- ///
- public string Description
- {
- get { return m_Description; }
- set
- {
- if (string.IsNullOrEmpty(value))
- m_Description = "";
- else
- m_Description = value;
- }
- }
- ///
- /// Data member for the Description property
- ///
- protected string m_Description = "";
-
- ///
- /// Address
- ///
- public string Address
- {
- get { return m_Address; }
- set
- {
- if (string.IsNullOrEmpty(value))
- m_Address = "";
- else
- m_Address = value;
- }
- }
- ///
- /// Data member for the Address property
- ///
- protected string m_Address = "";
-
- ///
- /// The geography instance which describes this placemark
- ///
- public Geography Geography
- {
- get { return m_Geography; }
- set { m_Geography = value; }
- }
- ///
- /// Data member for the Geography property
- ///
- protected Geography m_Geography;
-
- ///
- /// The geography instance where this placemark looks at
- ///
- public Geography LookAt
- {
- get { return m_LookAt; }
- set { m_LookAt = value; }
- }
- ///
- /// Data member for the LookAt property
- ///
- protected Geography m_LookAt;
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/Point.cs b/KMLProcessor/Import/Point.cs
deleted file mode 100644
index 88ec397..0000000
--- a/KMLProcessor/Import/Point.cs
+++ /dev/null
@@ -1,219 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class holds the information about a Point extracted from the KML file
- ///
- public class Point : Geography
- {
- #region Constructors
-
- ///
- /// Default constructor
- ///
- public Point()
- : this(0, 0, null, null)
- {
- }
-
- ///
- /// Constructor. Constructs the point using the given longitude and latitude.
- ///
- /// Longitude
- /// Latitude
- public Point(double longitude, double latitude)
- : this(longitude, latitude, null, null)
- {
- }
-
- ///
- /// Constructor. Constructs the point using the given longitude, latitude and altitude
- ///
- /// Longitude
- /// Latitude
- /// Altitude
- public Point(double longitude, double latitude, double? altitude)
- : this(longitude, latitude, altitude, null)
- {
- }
-
- ///
- /// Constructor. Constructs the point using the given longitude, latitude, altitude and measure.
- ///
- /// Longitude
- /// Latitude
- /// Altitude
- /// Measure
- public Point(double longitude, double latitude, double? altitude, double? measure)
- {
- Longitude = longitude;
- Latitude = latitude;
- Altitude = altitude;
- Measure = measure;
- }
-
- #endregion
-
- #region Public Properties
-
- ///
- /// Longitude.
- ///
- public double Longitude
- {
- get { return m_Longitude; }
- set
- {
- m_Longitude = value;
- }
- }
- ///
- /// Data member for the Longitude property
- ///
- protected double m_Longitude = 0;
-
- ///
- /// Latitude.
- ///
- public double Latitude
- {
- get { return m_Latitude; }
- set
- {
- m_Latitude = value;
- }
- }
- ///
- /// Data member for the Latitude property
- ///
- protected double m_Latitude = 0;
-
- ///
- /// Altitude.
- ///
- public double? Altitude
- {
- get { return m_Altitude; }
- set
- {
- m_Altitude = value;
- }
- }
- ///
- /// Data member for the Altitude property
- ///
- protected double? m_Altitude = null;
-
- ///
- /// Measure
- ///
- public double? Measure
- {
- get { return m_Measure; }
- set { m_Measure = value; }
- }
- ///
- /// Data member for the Measure property
- ///
- protected double? m_Measure = null;
-
- ///
- /// SqlGeography instance well-known text.
- ///
- public override string WKT
- {
- get
- {
- string wkt = String.Format("POINT({0})", Coordinate);
-
- return wkt;
- }
- }
-
- ///
- /// Point's coordinate in the format: longitude, latitude [, altitude]
- ///
- public string Coordinate
- {
- get
- {
- string wkt = String.Format("{0} {1}", m_Longitude, m_Latitude);
-
- if (m_Altitude.HasValue)
- wkt += " " + m_Altitude.Value;
-
- if (m_Measure.HasValue)
- wkt += " " + m_Measure.Value;
-
- return wkt;
- }
- }
-
- #endregion
-
- #region Geography methods
-
- ///
- /// This method populates the given sink with the data about this geography instance
- ///
- /// Sink to be populated
- public override void Populate(IGeographySink sink)
- {
- sink.BeginGeography(OpenGisGeographyType.Point);
- sink.BeginFigure(Latitude, Longitude, Altitude, Measure);
-
- sink.EndFigure();
- sink.EndGeography();
- }
-
- #endregion
-
- #region Public Methods
-
- ///
- /// This method clones this point
- ///
- /// Clone of this point
- public Point Clone()
- {
- Point c = new Point();
-
- c.Longitude = Longitude;
- c.Latitude = Latitude;
- c.Altitude = Altitude;
- c.Measure = Measure;
-
- return c;
- }
-
- #endregion
-
- #region Overriden Base Methods
-
- ///
- /// This method compares the given object with this point
- ///
- /// Object to be compared with this point
- ///
- public override bool Equals(object obj)
- {
- if (this == obj)
- return true;
-
- Point rhs = obj as Point;
- if (rhs == null)
- return false;
-
- return (m_Longitude == rhs.m_Longitude &&
- m_Latitude == rhs.m_Latitude &&
- ((m_Altitude == null && rhs.m_Altitude == null) || (m_Altitude.Value == rhs.m_Altitude.Value)) &&
- ((m_Measure == null && rhs.m_Measure == null) || (m_Measure.Value == rhs.m_Measure.Value)));
- }
-
- #endregion
- }
-}
diff --git a/KMLProcessor/Import/Polygon.cs b/KMLProcessor/Import/Polygon.cs
deleted file mode 100644
index b561468..0000000
--- a/KMLProcessor/Import/Polygon.cs
+++ /dev/null
@@ -1,135 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class contains the information about a polygon extracted from the KML file
- ///
- public class Polygon : Geography
- {
- #region Public Properties
-
- ///
- /// Outer border
- ///
- public LinearRing OuterRing
- {
- get { return m_OuterRing; }
- set
- {
- m_OuterRing = value;
- }
- }
- ///
- /// Data member for the OuterRing property
- ///
- protected LinearRing m_OuterRing = null;
-
- ///
- /// Inner borders/rings
- ///
- public IList InnerRing
- {
- get { return m_InnerRing; }
- }
- ///
- /// Data member for the InnerRing property
- ///
- public List m_InnerRing = new List();
-
- ///
- /// SqlGeography instance well-known text.
- ///
- public override string WKT
- {
- get
- {
- string wkt = "POLYGON(";
-
- if (m_OuterRing != null)
- wkt += m_OuterRing.Vertices;
- else
- throw new Exception("Outer ring is not set.");
-
- foreach (LinearRing ir in m_InnerRing)
- {
- wkt += ", " + ir.Vertices;
- }
-
- wkt += ")";
-
- return wkt;
- }
- }
-
- #endregion
-
- #region Geography Methods
-
- ///
- /// This method populates the given sink with the data from this geography instance
- ///
- /// Sink to be populated
- public override void Populate(IGeographySink sink)
- {
- if (this.OuterRing == null || this.OuterRing.Points == null || this.OuterRing.Points.Count == 0)
- return;
-
- sink.BeginGeography(OpenGisGeographyType.Polygon);
-
- // Populates the outer boundary
- sink.BeginFigure(
- this.OuterRing.Points[0].Latitude,
- this.OuterRing.Points[0].Longitude,
- this.OuterRing.Points[0].Altitude,
- this.OuterRing.Points[0].Measure);
-
- for (int i = 1; i < this.OuterRing.Points.Count; i++)
- {
- sink.AddLine(
- this.OuterRing.Points[i].Latitude,
- this.OuterRing.Points[i].Longitude,
- this.OuterRing.Points[i].Altitude,
- this.OuterRing.Points[i].Measure);
- }
-
- sink.EndFigure();
-
- if (this.InnerRing != null && this.InnerRing.Count > 0)
- {
- // Populates the inner boundaries
-
- for (int j = 0; j < this.InnerRing.Count; j++)
- {
- if (this.InnerRing[j].Points == null || this.InnerRing[j].Points.Count == 0)
- continue;
-
- sink.BeginFigure(
- this.InnerRing[j].Points[0].Latitude,
- this.InnerRing[j].Points[0].Longitude,
- this.InnerRing[j].Points[0].Altitude,
- this.InnerRing[j].Points[0].Measure);
-
- for (int i = 1; i < this.InnerRing[j].Points.Count; i++)
- {
- sink.AddLine(
- this.InnerRing[j].Points[i].Latitude,
- this.InnerRing[j].Points[i].Longitude,
- this.InnerRing[j].Points[i].Altitude,
- this.InnerRing[j].Points[i].Measure);
- }
-
- sink.EndFigure();
- }
- }
-
- sink.EndGeography();
- }
-
- #endregion
- }
-}
-
diff --git a/KMLProcessor/Import/Region.cs b/KMLProcessor/Import/Region.cs
deleted file mode 100644
index dd5cb70..0000000
--- a/KMLProcessor/Import/Region.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class holds the information about a Region extracted from the KML file
- ///
- public class Region : Geography
- {
- #region Public Data
-
- ///
- /// Box which defines the border of this region
- ///
- public Geography Box
- {
- get { return m_Box; }
- set { m_Box = value; }
- }
- ///
- /// Data member for the Box property
- ///
- protected Geography m_Box;
-
- ///
- /// SqlGeography instance well-known text.
- ///
- public override string WKT
- {
- get
- {
- if (Box != null)
- {
- return Box.WKT;
- }
-
- throw new KMLException("WKT is not defined.");
- }
- }
-
- #endregion
-
- #region Geography Methods
-
- ///
- /// This method populates the given sink with the data from this geography instance
- ///
- /// Sink to be populated
- public override void Populate(Microsoft.SqlServer.Types.IGeographySink sink)
- {
- if (Box != null)
- {
- Box.Populate(sink);
- }
- }
-
- #endregion
- }
-}
diff --git a/KMLProcessor/KmlFunctions.cs b/KMLProcessor/KmlFunctions.cs
deleted file mode 100644
index 522d835..0000000
--- a/KMLProcessor/KmlFunctions.cs
+++ /dev/null
@@ -1,275 +0,0 @@
-using System;
-using System.Data;
-using System.Data.SqlClient;
-using System.Data.SqlTypes;
-using Microsoft.SqlServer.Server;
-using Microsoft.SqlServer.Types;
-using Microsoft.SqlServer.SpatialToolbox.KMLProcessor;
-using System.IO;
-using System.Xml;
-using System.Collections;
-using System.Collections.ObjectModel;
-
-///
-/// This class contains the KML import/export functions
-///
-public partial class KMLFunctions
-{
- #region Public Methods
-
- #region Import Methods
-
- ///
- /// This function parses the given KML string and returns the extracted geography instance.
- ///
- /// KML string to be parsed
- /// If true and the extracted geography instance is invalid then the MakeValid
- /// function will be executed on the extracted geography instance
- /// Extracted geography instance
- [Microsoft.SqlServer.Server.SqlFunction]
- public static SqlGeography KmlToGeography(
- SqlString kml,
- SqlBoolean makeValid)
- {
- if (kml == null || string.IsNullOrEmpty(kml.Value))
- {
- return SqlGeography.Null;
- }
-
- SqlGeographyBuilder constructed = new SqlGeographyBuilder();
- Microsoft.SqlServer.SpatialToolbox.KMLProcessor.KMLProcessor parser = new Microsoft.SqlServer.SpatialToolbox.KMLProcessor.KMLProcessor(kml.Value.Trim());
- parser.Populate(constructed, makeValid.Value);
- return constructed.ConstructedGeography;
- }
-
- #endregion
-
- #region TVF Import Functions
-
- ///
- /// This TVF function parses the given KML string. All geography instances will be extracted and
- /// returned along with the context where they were found.
- /// One record will be returned for each extracted geography instance.
- ///
- /// The return table format is: (Id NVARCHAR(200), Context NVARCHAR(MAX), Shape Geography)
- ///
- /// KML string which should be processed
- /// If true and extracted geography instance is invalid then the MakeValid
- /// function will be executed on that geography instance
- [Microsoft.SqlServer.Server.SqlFunction(
- FillRowMethodName = "DecodeGeographyContext",
- TableDefinition = "Id NVARCHAR(200), Context NVARCHAR(MAX), Shape Geography"
- )]
- public static IEnumerable ImportKml(
- SqlString kml,
- SqlBoolean makeValid)
- {
- if (kml == null || string.IsNullOrEmpty(kml.Value))
- return new Collection();
-
- Collection geographies = new Collection();
-
- Microsoft.SqlServer.SpatialToolbox.KMLProcessor.KMLProcessor parser =
- new Microsoft.SqlServer.SpatialToolbox.KMLProcessor.KMLProcessor(kml.Value.Trim());
-
- foreach (Geography p in parser.Geographies)
- {
- GeographyContext ge = new GeographyContext();
- ge.Id = p.Id;
- ge.Context = p.Context;
- ge.Shape = p.ToSqlGeography(makeValid.Value);
-
- geographies.Add(ge);
- }
-
- return geographies;
- }
-
- ///
- /// This method is a helper method for the TVF function ImportKml. A Sql Server uses it to decode a row
- /// returned by the ImportKml function.
- ///
- /// A row returned by the ImportKml function
- /// Id extracted from a rowData
- /// Context extracted from a rowData
- /// Shape extracted from a rowData
- public static void DecodeGeographyContext(
- object rowData,
- out SqlString Id,
- out SqlString Context,
- out SqlGeography Shape)
- {
- GeographyContext data = rowData as GeographyContext;
-
- Id = data.Id;
- Context = data.Context;
- Shape = data.Shape;
- }
-
- #endregion
-
- #region Export Methods
-
- ///
- /// This method converts the given geography instance into the KML format
- ///
- /// Geography instance to be converted
- /// KML representation of the given geograpky instance
- [Microsoft.SqlServer.Server.SqlFunction]
- public static SqlString ExportToKml(SqlGeography g)
- {
- if (g == null || g.IsNull)
- return new SqlString("");
-
- MemoryStream stream = new MemoryStream();
- XmlWriter writer = XmlWriter.Create(stream);
-
- KeyholeMarkupLanguageGeography sink = new KeyholeMarkupLanguageGeography(writer);
-
- g.Populate(sink);
-
- sink.FinalizeKMLDocument();
-
- writer.Flush();
- stream.Seek(0, SeekOrigin.Begin);
- byte[] ba = stream.ToArray();
- char[] sData = System.Text.ASCIIEncoding.UTF8.GetChars(ba);
- string s = new string(sData);
- writer.Close();
-
- // Removes everything before
- if (s.IndexOf("") > 0)
- s = s.Remove(0, s.IndexOf(""));
-
- return new SqlString(s);
- }
-
- ///
- /// This method converts the given geography instance represented as a WKT string, into the string in the KML format.
- ///
- /// Geography instance represented as the WKT string
- /// The KML string
- [Microsoft.SqlServer.Server.SqlFunction]
- public static SqlString ExportWKTToKml(SqlString wkt)
- {
- if (wkt == null || string.IsNullOrEmpty(wkt.Value))
- return new SqlString("");
-
- SqlGeography g = SqlGeography.Parse(wkt);
- return ExportToKml(g);
- }
-
- #endregion
-
- #endregion
-
- #region Protected Methods
-
- ///
- /// This method stores the given box into the database
- ///
- /// Box to be saved
- /// The KmlId to connect this object to
- /// Sql connection
- /// Sql transaction
- /// The database id which is assigned to the given box
- protected static int? SaveLatLonBox(Geography box, int insertedKmlId, SqlConnection con, SqlTransaction tran)
- {
- #region Insert Geography
-
- int? insertedGeographyId = null;
-
- if (box != null)
- {
- SqlCommand saveGeographyCmd =
- new SqlCommand(
- " insert into Geographies(Geography, KmlId) " +
- " values(geography::Parse('" + box.ToString() + "'), @KmlId);select scope_identity()", con, tran);
-
- AddParameter(saveGeographyCmd, "KmlId", insertedKmlId, DbType.Int32);
-
- insertedGeographyId = (int)((decimal)saveGeographyCmd.ExecuteScalar());
- box.DbId = insertedGeographyId;
- }
-
- #endregion
-
- #region Insert Lat Lon Box
-
- SqlCommand saveLatLonBox =
- new SqlCommand(
- " insert into LatLonBoxes(BoxType, GeographyId, rotation, MinAlt, MaxAlt, AltitudeMode) " +
- " values(@BoxType, @GeographyId, @rotation, @MinAlt, @MaxAlt, @AltitudeMode);select scope_identity()",
- con, tran);
-
- AddParameter(saveLatLonBox, "GeographyId", insertedGeographyId, DbType.Int32);
-
- LatLonBox latLonBox = box as LatLonBox;
- LatLonAltBox latLonAltBox = box as LatLonAltBox;
- LatLonQuad latLonQuad = box as LatLonQuad;
-
- string BoxType = "";
-
- if (latLonBox != null)
- {
- BoxType = "LatLonBox";
- AddParameter(saveLatLonBox, "rotation", latLonBox.Rotation, DbType.Double);
- }
- else
- {
- AddParameter(saveLatLonBox, "rotation", null, DbType.Double);
- }
-
- if (latLonAltBox != null)
- {
- BoxType = "LatLonAltBox";
-
- AddParameter(saveLatLonBox, "MinAlt", latLonAltBox.MinAltitude, DbType.Double);
- AddParameter(saveLatLonBox, "MaxAlt", latLonAltBox.MaxAltitude, DbType.Double);
- AddParameter(saveLatLonBox, "AltitudeMode", latLonAltBox.AltitudeMode.ToString(), DbType.String);
- }
- else
- {
- AddParameter(saveLatLonBox, "MinAlt", null, DbType.Double);
- AddParameter(saveLatLonBox, "MaxAlt", null, DbType.Double);
- AddParameter(saveLatLonBox, "AltitudeMode", null, DbType.Double);
- }
-
- if (latLonQuad != null)
- {
- BoxType = "LatLonQuad";
- }
-
- AddParameter(saveLatLonBox, "BoxType", BoxType, DbType.String);
-
- int? insertedLatLonBox = (int)((decimal)saveLatLonBox.ExecuteScalar());
-
- #endregion
-
- return insertedLatLonBox;
- }
-
- ///
- /// This method adds a new parameter to the given sql command
- ///
- /// Command to add parameter to
- /// Parameter name
- /// Parameter value
- /// Parameter database type
- protected static void AddParameter(SqlCommand cmd, string parameterName, object value, DbType type)
- {
- if (value == null)
- value = DBNull.Value;
-
- cmd.Parameters.Add(new SqlParameter()
- {
- ParameterName = parameterName,
- Value = value,
- DbType = type
- });
- }
-
- #endregion
-}
-
-
diff --git a/KMLProcessor/Utilities.cs b/KMLProcessor/Utilities.cs
deleted file mode 100644
index 8cdc15d..0000000
--- a/KMLProcessor/Utilities.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Xml;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class contains the utility methods
- ///
- public class Utilities
- {
- ///
- /// This method shifts the given value into the range [-upperBorder, upperBorder]
- ///
- /// The value to be shifted
- /// The range's upper bound
- /// The shifted value
- public static double ShiftInRange(double value, double upperBorder)
- {
- upperBorder = Math.Abs(upperBorder);
-
- if (upperBorder == 0)
- throw new KMLException("The range has to be greater then 0.");
-
- double rangeSize = 2 * upperBorder;
-
- if (value > upperBorder)
- {
- int k = (int)((value + upperBorder) / rangeSize);
- value = value - k * rangeSize;
- }
- else if (value < -1 * upperBorder)
- {
- int k = (int)((value - upperBorder) / rangeSize);
- value = value - k * rangeSize;
- }
-
- return value;
- }
- }
-}
diff --git a/Projections/AlbersEqualAreaProjection.cs b/Projections/AlbersEqualAreaProjection.cs
deleted file mode 100644
index aa7b303..0000000
--- a/Projections/AlbersEqualAreaProjection.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//
-// References: http://mathworld.wolfram.com/AlbersEqual-AreaConicProjection.html
-//------------------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-
-namespace SQLSpatialTools
-{
- // EPSG Code: 9822
- // OGC WKT Name: Albers_Conic_Equal_Area
- internal sealed class AlbersEqualAreaProjection : Projection
- {
- // longitude0, latitude0, parallel1, parallel2
- public AlbersEqualAreaProjection(Dictionary parameters)
- : base(parameters)
- {
- double latitude0_rad = InputLatitude("latitude0");
- double parallel1_rad = InputLatitude("parallel1");
- double parallel2_rad = InputLatitude("parallel2");
-
- double cos_parallel1 = Math.Cos(parallel1_rad);
- double sin_parallel1 = Math.Sin(parallel1_rad);
-
- _n2 = sin_parallel1 + Math.Sin(parallel2_rad);
- if (Math.Abs(_n2) <= MathX.Tolerance)
- {
- throw new ArgumentOutOfRangeException();
- }
-
- _n = _n2 / 2;
- _n_half = _n2 / 4;
- _inv_n2 = 1 / _n2;
- _inv_n = 2 / _n2;
-
- _c = MathX.Square(cos_parallel1) + sin_parallel1 * _n2;
- _c_over_n2 = _c * _inv_n2;
-
- double a = _c - Math.Sin(latitude0_rad) * _n2;
- if (a < 0)
- {
- throw new ArgumentOutOfRangeException();
- }
- _ro_0 = Math.Sqrt(a) * _inv_n;
- }
-
- protected internal override void Project(double latitude, double longitude, out double x, out double y)
- {
- double a = _c - Math.Sin(latitude) * _n2;
- if (a < 0)
- {
- throw new ArgumentOutOfRangeException("latitude");
- }
-
- double ro = Math.Sqrt(a) * _inv_n;
-
- double tetha = longitude * _n;
- x = ro * Math.Sin(tetha);
- y = _ro_0 - ro * Math.Cos(tetha);
- }
-
- protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
- {
- double ros = x * x + (_ro_0 - y) * (_ro_0 - y);
- double a = _c_over_n2 - ros * _n_half;
- if (Double.IsNaN(a) || Math.Abs(a) > 1 + MathX.Tolerance)
- {
- throw new ArgumentOutOfRangeException("x, y");
- }
-
- latitude = Math.Asin(MathX.Clamp(1, a));
- longitude = MathX.Clamp(Math.PI, MathX.Atan2(x, _ro_0 - y, "x, y") * _inv_n);
- }
-
- private readonly double _c;
- private readonly double _c_over_n2;
- private readonly double _ro_0;
- private readonly double _n2;
- private readonly double _n;
- private readonly double _n_half;
- private readonly double _inv_n2;
- private readonly double _inv_n;
- }
-}
\ No newline at end of file
diff --git a/Projections/LambertConformalConicProjection.cs b/Projections/LambertConformalConicProjection.cs
deleted file mode 100644
index 04ffaa9..0000000
--- a/Projections/LambertConformalConicProjection.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//
-// References: http://mathworld.wolfram.com/LambertConformalConicProjection.html
-//------------------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-
-namespace SQLSpatialTools
-{
- internal sealed class LambertConformalConicProjection : Projection
- {
- // Angles are in degrees.
- // longitude0:
- // latitude: latitude for the origin of cartesian coordinates
- // fi1: standard parallel
- // fi2: standard parallel
- //
- public LambertConformalConicProjection(Dictionary parameters)
- : base(parameters)
- {
- double latitude_rad = InputLatitude("latitude0");
- double fi1_rad = InputLatitude("fi1");
- double fi2_rad = InputLatitude("fi2");
-
- if (Math.Abs(fi1_rad - fi2_rad) <= MathX.Tolerance)
- {
- throw new ArgumentException(Resource.Fi1AndFi2MustBeDifferent);
- }
-
- // a != 0 and b != 0 because fi1 != fi2
- double a = Math.Log(Math.Cos(fi1_rad) / Math.Cos(fi2_rad));
- double b = Math.Log(Math.Tan(Math.PI / 4 + fi2_rad / 2) / Math.Tan(Math.PI / 4 + fi1_rad / 2));
- Debug.Assert(!Double.IsNaN(a) && !Double.IsInfinity(a) && Math.Abs(a) > 0, a.ToString(CultureInfo.InvariantCulture));
- Debug.Assert(!Double.IsNaN(b) && !Double.IsInfinity(b) && Math.Abs(b) > 0, b.ToString(CultureInfo.InvariantCulture));
-
- _n = a / b;
- _nInv = b / a;
-
- // tan_fi1 > 0 because fi1 is in range (-Pi/2, Pi/2)
- double tan_fi1 = Math.Tan(Math.PI / 4 + fi1_rad / 2);
- Debug.Assert(!Double.IsInfinity(tan_fi1) && tan_fi1 > 0, tan_fi1.ToString(CultureInfo.InvariantCulture));
- _f = Math.Cos(fi1_rad) * Math.Pow(tan_fi1, _n) * _nInv;
- Debug.Assert(Math.Abs(_f) > MathX.Tolerance);
-
- // tan_latitude > 0 because latitude is in range (-Pi/2, Pi/2)
- double tan_latitude = Math.Tan(Math.PI / 4 + latitude_rad / 2);
- Debug.Assert(!Double.IsInfinity(tan_latitude) && tan_latitude > 0, tan_latitude.ToString(CultureInfo.InvariantCulture));
- _ro_0 = _f * Math.Pow(tan_latitude, -_n);
- }
-
- protected internal override void Project(double latitude, double longitude, out double x, out double y)
- {
- double a = Math.Tan(Math.PI / 4 + latitude / 2);
- double ro = _f * (a <= MathX.Tolerance ? 0 : Math.Pow(a, -_n));
- double theta = _n * longitude;
-
- x = ro * Math.Sin(theta);
- y = _ro_0 - ro * Math.Cos(theta);
- }
-
- protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
- {
- double ro = Math.Sign(_n) * Math.Sqrt(x * x + MathX.Square(_ro_0 - y));
- // If ro is zero or very small then then latitude will be +-Pi/2 depending on the sign of f.
- latitude = 2 * Math.Atan(Math.Pow(_f / ro, _nInv)) - Math.PI / 2;
- longitude = MathX.Clamp(Math.PI, Math.Atan2(x, _ro_0 - y) * _nInv);
- }
-
- private readonly double _n;
- private readonly double _nInv; // = 1 / n
- private readonly double _f;
- private readonly double _ro_0;
- }
-}
\ No newline at end of file
diff --git a/Projections/ObliqueMercatorProjection.cs b/Projections/ObliqueMercatorProjection.cs
deleted file mode 100644
index 0f13efc..0000000
--- a/Projections/ObliqueMercatorProjection.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//
-// References: http://mathworld.wolfram.com/MercatorProjection.html
-//------------------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-
-namespace SQLSpatialTools
-{
- internal sealed class ObliqueMercatorProjection : Projection
- {
- private const double MaxY = 5;
-
- // Angles are in degrees.
- // longitude0:
- // fi1:
- // lambda1:
- // fi2:
- // lambda2:
- //
- public ObliqueMercatorProjection(Dictionary parameters)
- : base(parameters)
- {
- double fi1_rad = InputLatitude("fi1");
- double fi2_rad = InputLatitude("fi2");
-
- double lambda1_rad = InputLongitude("lambda1");
- double lambda2_rad = InputLongitude("lambda2");
-
- double cos_fi1 = Math.Cos(fi1_rad);
- double sin_fi1 = Math.Sin(fi1_rad);
-
- double cos_fi2 = Math.Cos(fi2_rad);
- double sin_fi2 = Math.Sin(fi2_rad);
-
- double a = cos_fi1 * sin_fi2 * Math.Cos(lambda1_rad);
- double b = sin_fi1 * cos_fi2 * Math.Cos(lambda2_rad);
- double c = sin_fi1 * cos_fi2 * Math.Sin(lambda2_rad);
- double d = cos_fi1 * sin_fi2 * Math.Sin(lambda1_rad);
- double lambda_p = Math.Atan2(a - b, c - d);
-
- double fi_p = Math.Atan2(-Math.Cos(lambda_p - lambda1_rad), Math.Tan(fi1_rad));
- _cos_fi_p = Math.Cos(fi_p);
- _sin_fi_p = Math.Sin(fi_p);
- }
-
- protected internal override void Project(double latitude, double longitude, out double x, out double y)
- {
- double sin_lat = Math.Sin(latitude);
- double cos_lat = Math.Cos(latitude);
-
- double sin_long = Math.Sin(longitude);
- double cos_long = Math.Cos(longitude);
-
- double a = _sin_fi_p * sin_lat - _cos_fi_p * cos_lat * sin_long; // sin_fi_p
- if (Double.IsNaN(a) || Math.Abs(a) > 1)
- {
- throw new ArgumentOutOfRangeException("latitude, longitude");
- }
-
- x = MathX.Atan2(sin_lat / cos_lat * _cos_fi_p + sin_long * _sin_fi_p, cos_long, "latitude, longitude");
- y = MathX.Clamp(MaxY, Math.Log((1 + a) / (1 - a)) / 2); // protect againt +-Infinity
- }
-
- protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
- {
- double sin_x = Math.Sin(x);
- double cos_x = Math.Cos(x);
-
- double sinh_y = Math.Sinh(y);
- double cosh_y = Math.Cosh(y);
-
- // cosh_y >= 1 by definition
- double a = (_sin_fi_p * sinh_y + _cos_fi_p * sin_x) / cosh_y;
- if (Math.Abs(a) > 1)
- {
- throw new ArgumentOutOfRangeException("x, y");
- }
-
- latitude = Math.Asin(a);
- longitude = MathX.Atan2(_sin_fi_p * sin_x - _cos_fi_p * sinh_y, cos_x, "x, y");
- }
-
- private readonly double _cos_fi_p; // = cos(fi_p)
- private readonly double _sin_fi_p; // = sin(fi_p)
- }
-}
\ No newline at end of file
diff --git a/SQL Scripts/Register.sql b/SQL Scripts/Register.sql
deleted file mode 100644
index bbdd72b..0000000
--- a/SQL Scripts/Register.sql
+++ /dev/null
@@ -1,96 +0,0 @@
--- Copyright (c) Microsoft Corporation. All rights reserved.
-
--- Install the SQLSpatialTools assembly and all its functions into the current database
-
--- Enable CLR
-sp_configure 'clr enabled', 1
-reconfigure
-go
-
--- !!! Insert the path to the SQLSpatialTools assembly here !!!
-create assembly SQLSpatialTools from ''
-go
-
-
--- Create types
-create type Projection external name SQLSpatialTools.[SQLSpatialTools.SqlProjection]
-go
-
-create type AffineTransform external name SQLSpatialTools.[SQLSpatialTools.AffineTransform]
-go
-
-
--- Register the functions...
-create function ShiftGeometry(@g geometry, @xShift float, @yShift float) returns geometry
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].ShiftGeometry
-go
-
-create function LocateAlongGeog(@g geography, @distance float) returns geography
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].LocateAlongGeog
-go
-
-create function LocateAlongGeom(@g geometry, @distance float) returns geometry
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].LocateAlongGeom
-go
-
-create function InterpolateBetweenGeog(@start geography, @end geography, @distance float) returns geography
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].InterpolateBetweenGeog
-go
-
-create function InterpolateBetweenGeom(@start geometry, @end geometry, @distance float) returns geometry
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].InterpolateBetweenGeom
-go
-
-create function VacuousGeometryToGeography(@toConvert geometry, @targetSrid int) returns geography
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].VacuousGeometryToGeography
-go
-
-create function VacuousGeographyToGeometry(@toConvert geography, @targetSrid int) returns geometry
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].VacuousGeographyToGeometry
-go
-
-create function ConvexHullGeography(@geog geography) returns geography
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].ConvexHullGeography
-go
-
-create function ConvexHullGeographyFromText(@inputWKT nvarchar(max), @srid int) returns geography
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].ConvexHullGeographyFromText
-go
-
-create function IsValidGeographyFromGeometry(@inputGeometry geometry) returns bit
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].IsValidGeographyFromGeometry
-go
-
-create function IsValidGeographyFromText(@inputWKT nvarchar(max), @srid int) returns bit
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].IsValidGeographyFromText
-go
-
-create function MakeValidGeographyFromGeometry(@inputGeometry geometry) returns geography
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].MakeValidGeographyFromGeometry
-go
-
-create function MakeValidGeographyFromText(@inputWKT nvarchar(max), @srid int) returns geography
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].MakeValidGeographyFromText
-go
-
-create function FilterArtifactsGeometry(@g geometry, @filterEmptyShapes bit, @filterPoints bit, @lineStringTolerance float(53), @ringTolerance float(53)) returns geometry
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].FilterArtifactsGeometry
-go
-
-create function FilterArtifactsGeography(@g geography, @filterEmptyShapes bit, @filterPoints bit, @lineStringTolerance float(53), @ringTolerance float(53)) returns geography
-as external name SQLSpatialTools.[SQLSpatialTools.Functions].FilterArtifactsGeography
-go
-
--- Create aggregates.
-
-create aggregate GeometryEnvelopeAggregate(@geom geometry) returns geometry
-external name SQLSpatialTools.[SQLSpatialTools.GeometryEnvelopeAggregate]
-go
-
-create aggregate GeographyCollectionAggregate(@geog geography) returns geography
-external name SQLSpatialTools.[SQLSpatialTools.GeographyCollectionAggregate]
-go
-
-create aggregate GeographyUnionAggregate(@geog geography) returns geography
-external name SQLSpatialTools.[SQLSpatialTools.GeographyUnionAggregate]
-go
\ No newline at end of file
diff --git a/SQL Scripts/Unregister.sql b/SQL Scripts/Unregister.sql
deleted file mode 100644
index c231e52..0000000
--- a/SQL Scripts/Unregister.sql
+++ /dev/null
@@ -1,36 +0,0 @@
--- Copyright (c) Microsoft Corporation. All rights reserved.
-
--- Drop the SQLSpatialTools assembly and all its functions from the current database
-
--- Drop the aggregates...
-drop aggregate GeometryEnvelopeAggregate
-drop aggregate GeographyCollectionAggregate
-drop aggregate GeographyUnionAggregate
-
--- Drop the functions...
-drop function ShiftGeometry
-drop function LocateAlongGeog
-drop function LocateAlongGeom
-drop function InterpolateBetweenGeog
-drop function InterpolateBetweenGeom
-drop function VacuousGeometryToGeography
-drop function VacuousGeographyToGeometry
-
-drop function ConvexHullGeography
-drop function ConvexHullGeographyFromText
-drop function IsValidGeographyFromGeometry
-drop function IsValidGeographyFromText
-drop function MakeValidGeographyFromGeometry
-drop function MakeValidGeographyFromText
-
-drop function FilterArtifactsGeometry
-drop function FilterArtifactsGeography
-
--- Drop the types...
-drop type Projection
-drop type AffineTransform
-
--- Drop the assembly...
-drop assembly SQLSpatialTools
-
-
diff --git a/SQLSpatialTools.sln b/SQLSpatialTools.sln
index 8364fef..818f745 100644
--- a/SQLSpatialTools.sln
+++ b/SQLSpatialTools.sln
@@ -1,7 +1,21 @@
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLSpatialTools", "SQLSpatialTools.csproj", "{09428C16-7DAE-4C28-A853-E4F04DD0BEDD}"
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27130.2027
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLSpatialTools", "src\SQLSpatialTools.csproj", "{09428C16-7DAE-4C28-A853-E4F04DD0BEDD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLSpatialTools.UnitTests", "unittest\SQLSpatialTools.UnitTests.csproj", "{5FCDB549-1B10-4115-9A3F-AA55584FEA23}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{FDEEA275-E34A-465E-836C-F297B4FECDB9}"
+ ProjectSection(SolutionItems) = preProject
+ License.txt = License.txt
+ Readme.txt = Readme.txt
+ SpatialTools.pfx = SpatialTools.pfx
+ SQLSpatialTools.sln.DotSettings = SQLSpatialTools.sln.DotSettings
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLSpatialTools.UnitTests.Extension", "unittest-extension\SQLSpatialTools.UnitTests.Extension.csproj", "{98EFE72E-E303-4BDE-B86F-5F704DB3F5BE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -13,8 +27,31 @@ Global
{09428C16-7DAE-4C28-A853-E4F04DD0BEDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{09428C16-7DAE-4C28-A853-E4F04DD0BEDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{09428C16-7DAE-4C28-A853-E4F04DD0BEDD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5FCDB549-1B10-4115-9A3F-AA55584FEA23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5FCDB549-1B10-4115-9A3F-AA55584FEA23}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5FCDB549-1B10-4115-9A3F-AA55584FEA23}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5FCDB549-1B10-4115-9A3F-AA55584FEA23}.Release|Any CPU.Build.0 = Release|Any CPU
+ {98EFE72E-E303-4BDE-B86F-5F704DB3F5BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {98EFE72E-E303-4BDE-B86F-5F704DB3F5BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {98EFE72E-E303-4BDE-B86F-5F704DB3F5BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {98EFE72E-E303-4BDE-B86F-5F704DB3F5BE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {28138FE6-3CEC-4C1C-BFCA-35186C9246EF}
+ EndGlobalSection
+ GlobalSection(Performance) = preSolution
+ HasPerformanceSessions = true
+ EndGlobalSection
+ GlobalSection(Performance) = preSolution
+ HasPerformanceSessions = true
+ EndGlobalSection
+ GlobalSection(Performance) = preSolution
+ HasPerformanceSessions = true
+ EndGlobalSection
+ GlobalSection(Performance) = preSolution
+ HasPerformanceSessions = true
+ EndGlobalSection
EndGlobal
diff --git a/SQLSpatialTools.sln.DotSettings b/SQLSpatialTools.sln.DotSettings
new file mode 100644
index 0000000..c2ca56a
--- /dev/null
+++ b/SQLSpatialTools.sln.DotSettings
@@ -0,0 +1,46 @@
+
+ ABBC
+ AC
+ BC
+ BCAC
+ DB
+ GIS
+ KML
+ LRS
+ LSLS
+ LSMLS
+ MLSLS
+ MLSMLS
+ MSSQL
+ OGC
+ OSS
+ SQL
+ SRID
+ ST
+ WKT
+ XY
+ XYM
+ XYZ
+ <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Types.LRSLine" FunctionMask="System.Collections.IEnumerable.GetEnumerator" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Types.LRSMultiLine" FunctionMask="System.Collections.IEnumerable.GetEnumerator" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.LocateMAlongGeometrySink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.PopulateGeometryMeasuresSink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.SplitGeometrySegmentSink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.ReverseAndTranslateGeometrySink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.GeometryPointFilter" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.ReverseLinearGeometrySink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.GeometryToPointGeographySink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.ScaleMeasureGeometrySink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.BuildLRSMultiLineSink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.TranslateMeasureGeometrySink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.ClipMGeometrySegmentSink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.LineStringMergeGeometrySink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.ShiftGeometrySink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.VacuousGeometryToGeographySink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.UnProjector" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.ResetMGeometrySink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.BuildMultiLineFromLinesSink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.ValidateLinearMeasureGeometrySink" FunctionMask="AddCircularArc" IsEnabled="True" /><Filter ModuleMask="SQLSpatialTools" ModuleVersionMask="*" ClassMask="SQLSpatialTools.Sinks.Geometry.LocateAlongGeometrySink" FunctionMask="AddCircularArc" IsEnabled="True" /></ExcludeFilters></data>
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+
\ No newline at end of file
diff --git a/SQLSpatialTools.suo b/SQLSpatialTools.suo
deleted file mode 100644
index f71ef82..0000000
Binary files a/SQLSpatialTools.suo and /dev/null differ
diff --git a/Sinks/GeographyFilters.cs b/Sinks/GeographyFilters.cs
deleted file mode 100644
index 448c1a1..0000000
--- a/Sinks/GeographyFilters.cs
+++ /dev/null
@@ -1,345 +0,0 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2010 Microsoft Corporation.
-//------------------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- public class GeographyEmptyShapeFilter : IGeographySink
- {
- private IGeographySink m_sink;
- private Queue m_types = new Queue();
- private bool m_root = true;
-
- public GeographyEmptyShapeFilter(IGeographySink sink)
- {
- m_sink = sink;
- }
-
- public void SetSrid(int srid)
- {
- m_sink.SetSrid(srid);
- }
-
- public void BeginGeography(OpenGisGeographyType type)
- {
- if (m_root)
- {
- m_root = false;
- m_sink.BeginGeography(type);
- }
- else
- {
- m_types.Enqueue(type);
- }
- }
-
- public void EndGeography()
- {
- if (m_types.Count > 0)
- m_types.Dequeue();
- else
- m_sink.EndGeography();
- }
-
- public void BeginFigure(double latitude, double longitude, double? z, double? m)
- {
- while (m_types.Count > 0)
- m_sink.BeginGeography(m_types.Dequeue());
- m_sink.BeginFigure(latitude, longitude, z, m);
- }
-
- public void AddLine(double latitude, double longitude, double? z, double? m)
- {
- m_sink.AddLine(latitude, longitude, z, m);
- }
-
- public void EndFigure()
- {
- m_sink.EndFigure();
- }
- }
-
- public class GeographyPointFilter : IGeographySink
- {
- private IGeographySink m_sink;
- private int m_depth;
- private bool m_root = true;
-
- public GeographyPointFilter(IGeographySink sink)
- {
- m_sink = sink;
- }
-
- public void SetSrid(int srid)
- {
- m_sink.SetSrid(srid);
- }
-
- public void BeginGeography(OpenGisGeographyType type)
- {
- if (type == OpenGisGeographyType.Point || type == OpenGisGeographyType.MultiPoint)
- {
- if (m_root)
- {
- m_root = false;
- m_sink.BeginGeography(OpenGisGeographyType.GeometryCollection);
- m_sink.EndGeography();
- }
- m_depth++;
- }
- else
- {
- m_root = false;
- m_sink.BeginGeography(type);
- }
- }
-
- public void EndGeography()
- {
- if (m_depth > 0)
- m_depth--;
- else
- m_sink.EndGeography();
- }
-
- public void BeginFigure(double latitude, double longitude, double? z, double? m)
- {
- if (m_depth == 0)
- m_sink.BeginFigure(latitude, longitude, z, m);
- }
-
- public void AddLine(double latitude, double longitude, double? z, double? m)
- {
- if (m_depth == 0)
- m_sink.AddLine(latitude, longitude, z, m);
- }
-
- public void EndFigure()
- {
- if (m_depth == 0)
- m_sink.EndFigure();
- }
- }
-
- public class GeographyShortLineStringFilter : IGeographySink
- {
- private IGeographySink m_sink;
- private double m_tolerance;
- private int m_srid;
- private bool m_insideLineString;
- private List m_figure = new List();
-
- public GeographyShortLineStringFilter(IGeographySink sink, double tolerance)
- {
- m_sink = sink;
- m_tolerance = tolerance;
- }
-
- public void SetSrid(int srid)
- {
- m_srid = srid;
- m_sink.SetSrid(srid);
- }
-
- public void BeginGeography(OpenGisGeographyType type)
- {
- m_sink.BeginGeography(type);
- m_insideLineString = type == OpenGisGeographyType.LineString;
- }
-
- public void EndGeography()
- {
- m_sink.EndGeography();
- }
-
- public void BeginFigure(double latitude, double longitude, double? z, double? m)
- {
- if (m_insideLineString)
- {
- m_figure.Clear();
- m_figure.Add(new Vertex(latitude, longitude, z, m));
- }
- else
- {
- m_sink.BeginFigure(latitude, longitude, z, m);
- }
- }
-
- public void AddLine(double latitude, double longitude, double? z, double? m)
- {
- if (m_insideLineString)
- {
- m_figure.Add(new Vertex(latitude, longitude, z, m));
- }
- else
- {
- m_sink.AddLine(latitude, longitude, z, m);
- }
- }
-
- public void EndFigure()
- {
- if (m_insideLineString)
- {
- if (!IsShortLineString())
- {
- PopulateFigure(m_sink);
- }
- }
- else
- {
- m_sink.EndFigure();
- }
- }
-
- private bool IsShortLineString()
- {
- try
- {
- SqlGeographyBuilder b = new SqlGeographyBuilder();
- b.SetSrid(m_srid);
- b.BeginGeography(OpenGisGeographyType.LineString);
- PopulateFigure(b);
- b.EndGeography();
- SqlGeography g = b.ConstructedGeography;
- return g.STLength().Value < m_tolerance;
- }
- catch (FormatException) { }
- catch (ArgumentException) { }
- return false;
- }
-
- private void PopulateFigure(IGeographySink sink)
- {
- m_figure[0].BeginFigure(sink);
- for (int i = 1; i < m_figure.Count; i++)
- m_figure[i].AddLine(sink);
- sink.EndFigure();
- }
- }
-
- public class GeographyThinRingFilter : IGeographySink
- {
- private IGeographySink m_sink;
- private double m_tolerance;
- private bool m_insidePolygon;
- private int m_srid;
- private List m_figure = new List();
-
- public GeographyThinRingFilter(IGeographySink sink, double tolerance)
- {
- m_sink = sink;
- m_tolerance = tolerance;
- }
-
- public void SetSrid(int srid)
- {
- m_srid = srid;
- m_sink.SetSrid(srid);
- }
-
- public void BeginGeography(OpenGisGeographyType type)
- {
- m_sink.BeginGeography(type);
- m_insidePolygon = type == OpenGisGeographyType.Polygon;
- }
-
- public void EndGeography()
- {
- m_sink.EndGeography();
- }
-
- public void BeginFigure(double latitude, double longitude, double? z, double? m)
- {
- if (m_insidePolygon)
- {
- m_figure.Clear();
- m_figure.Add(new Vertex(latitude, longitude, z, m));
- }
- else
- {
- m_sink.BeginFigure(latitude, longitude, z, m);
- }
- }
-
- public void AddLine(double latitude, double longitude, double? z, double? m)
- {
- if (m_insidePolygon)
- {
- m_figure.Add(new Vertex(latitude, longitude, z, m));
- }
- else
- {
- m_sink.AddLine(latitude, longitude, z, m);
- }
- }
-
- public void EndFigure()
- {
- if (m_insidePolygon)
- {
- if (!IsThinRing())
- {
- PopulateFigure(m_sink, false);
- }
- }
- else
- {
- m_sink.EndFigure();
- }
- }
-
- private bool IsThinRing()
- {
- SqlGeography poly = RingToPolygon(true);
- if (poly == null)
- {
- // ring was not valid, try with different orientation
- poly = RingToPolygon(false);
- if (poly == null)
- {
- // if both orientations are invalid, we are dealing with very thin ring
- // so just return true
- return true;
- }
- }
- return poly.STArea().Value < m_tolerance * poly.STLength().Value;
- }
-
- private SqlGeography RingToPolygon(bool reverse)
- {
- try
- {
- SqlGeographyBuilder b = new SqlGeographyBuilder();
- b.SetSrid(m_srid);
- b.BeginGeography(OpenGisGeographyType.Polygon);
- PopulateFigure(b, reverse);
- b.EndGeography();
- return b.ConstructedGeography;
- }
- catch (FormatException) { }
- catch (ArgumentException) { }
- return null;
- }
-
- private void PopulateFigure(IGeographySink sink, bool reverse)
- {
- if (reverse)
- {
- m_figure[m_figure.Count - 1].BeginFigure(sink);
- for (int i = m_figure.Count - 2; i >= 0; i--)
- m_figure[i].AddLine(sink);
- }
- else
- {
- m_figure[0].BeginFigure(sink);
- for (int i = 1; i < m_figure.Count; i++)
- m_figure[i].AddLine(sink);
- }
- sink.EndFigure();
- }
- }
-}
\ No newline at end of file
diff --git a/Sinks/GeometryFilters.cs b/Sinks/GeometryFilters.cs
deleted file mode 100644
index 8f14c16..0000000
--- a/Sinks/GeometryFilters.cs
+++ /dev/null
@@ -1,339 +0,0 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2010 Microsoft Corporation.
-//------------------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- struct Vertex
- {
- double x;
- double y;
- double? z;
- double? m;
-
- public Vertex(double x_, double y_, double? z_, double? m_)
- {
- x = x_;
- y = y_;
- z = z_;
- m = m_;
- }
-
- public void BeginFigure(IGeometrySink sink) { sink.BeginFigure(x, y, z, m); }
- public void AddLine(IGeometrySink sink) { sink.AddLine(x, y, z, m); }
-
- public void BeginFigure(IGeographySink sink) { sink.BeginFigure(x, y, z, m); }
- public void AddLine(IGeographySink sink) { sink.AddLine(x, y, z, m); }
- }
-
- public class GeometryEmptyShapeFilter : IGeometrySink
- {
- private IGeometrySink m_sink;
- private Queue m_types = new Queue();
- private bool m_root = true;
-
- public GeometryEmptyShapeFilter(IGeometrySink sink)
- {
- m_sink = sink;
- }
-
- public void SetSrid(int srid)
- {
- m_sink.SetSrid(srid);
- }
-
- public void BeginGeometry(OpenGisGeometryType type)
- {
- if (m_root)
- {
- m_root = false;
- m_sink.BeginGeometry(type);
- }
- else
- {
- m_types.Enqueue(type);
- }
- }
-
- public void EndGeometry()
- {
- if (m_types.Count > 0)
- m_types.Dequeue();
- else
- m_sink.EndGeometry();
- }
-
- public void BeginFigure(double latitude, double longitude, double? z, double? m)
- {
- while (m_types.Count > 0)
- m_sink.BeginGeometry(m_types.Dequeue());
- m_sink.BeginFigure(latitude, longitude, z, m);
- }
-
- public void AddLine(double latitude, double longitude, double? z, double? m)
- {
- m_sink.AddLine(latitude, longitude, z, m);
- }
-
- public void EndFigure()
- {
- m_sink.EndFigure();
- }
- }
-
- public class GeometryPointFilter : IGeometrySink
- {
- private IGeometrySink m_sink;
- private int m_depth;
- private bool m_root = true;
-
- public GeometryPointFilter(IGeometrySink sink)
- {
- m_sink = sink;
- }
-
- public void SetSrid(int srid)
- {
- m_sink.SetSrid(srid);
- }
-
- public void BeginGeometry(OpenGisGeometryType type)
- {
- if (type == OpenGisGeometryType.Point || type == OpenGisGeometryType.MultiPoint)
- {
- if (m_root)
- {
- m_root = false;
- m_sink.BeginGeometry(OpenGisGeometryType.GeometryCollection);
- m_sink.EndGeometry();
- }
- m_depth++;
- }
- else
- {
- m_sink.BeginGeometry(type);
- }
- }
-
- public void EndGeometry()
- {
- if (m_depth > 0)
- m_depth--;
- else
- m_sink.EndGeometry();
- }
-
- public void BeginFigure(double x, double y, double? z, double? m)
- {
- if (m_depth == 0)
- m_sink.BeginFigure(x, y, z, m);
- }
-
- public void AddLine(double x, double y, double? z, double? m)
- {
- m_sink.AddLine(x, y, z, m);
- }
-
- public void EndFigure()
- {
- if (m_depth == 0)
- m_sink.EndFigure();
- }
- }
-
- public class GeometryShortLineStringFilter : IGeometrySink
- {
- private IGeometrySink m_sink;
- private double m_tolerance;
- private int m_srid;
- private bool m_insideLineString;
- private List m_figure = new List();
-
- public GeometryShortLineStringFilter(IGeometrySink sink, double tolerance)
- {
- m_sink = sink;
- m_tolerance = tolerance;
- }
-
- public void SetSrid(int srid)
- {
- m_srid = srid;
- m_sink.SetSrid(srid);
- }
-
- public void BeginGeometry(OpenGisGeometryType type)
- {
- m_sink.BeginGeometry(type);
- m_insideLineString = type == OpenGisGeometryType.LineString;
- }
-
- public void EndGeometry()
- {
- m_sink.EndGeometry();
- }
-
- public void BeginFigure(double x, double y, double? z, double? m)
- {
- if (m_insideLineString)
- {
- m_figure.Clear();
- m_figure.Add(new Vertex(x, y, z, m));
- }
- else
- {
- m_sink.BeginFigure(x, y, z, m);
- }
- }
-
- public void AddLine(double x, double y, double? z, double? m)
- {
- if (m_insideLineString)
- {
- m_figure.Add(new Vertex(x, y, z, m));
- }
- else
- {
- m_sink.AddLine(x, y, z, m);
- }
- }
-
- public void EndFigure()
- {
- if (m_insideLineString)
- {
- if (!IsShortLineString())
- {
- PopulateFigure(m_sink);
- }
- }
- else
- {
- m_sink.EndFigure();
- }
- }
-
- private bool IsShortLineString()
- {
- try
- {
- SqlGeometryBuilder b = new SqlGeometryBuilder();
- b.SetSrid(m_srid);
- b.BeginGeometry(OpenGisGeometryType.LineString);
- PopulateFigure(b);
- b.EndGeometry();
- return b.ConstructedGeometry.STLength().Value < m_tolerance;
- }
- catch (ArgumentException) { }
- catch (FormatException) { }
- return true;
- }
-
- private void PopulateFigure(IGeometrySink sink)
- {
- m_figure[0].BeginFigure(sink);
- for (int i = 1; i < m_figure.Count; i++)
- m_figure[i].AddLine(sink);
- sink.EndFigure();
- }
- }
-
- public class GeometryThinRingFilter : IGeometrySink
- {
- private IGeometrySink m_sink;
- private double m_tolerance;
- private bool m_insidePolygon;
- private int m_srid;
- private List m_figure = new List();
-
- public GeometryThinRingFilter(IGeometrySink sink, double tolerance)
- {
- m_sink = sink;
- m_tolerance = tolerance;
- }
-
- public void SetSrid(int srid)
- {
- m_srid = srid;
- m_sink.SetSrid(srid);
- }
-
- public void BeginGeometry(OpenGisGeometryType type)
- {
- m_sink.BeginGeometry(type);
- m_insidePolygon = type == OpenGisGeometryType.Polygon;
- }
-
- public void EndGeometry()
- {
- m_sink.EndGeometry();
- }
-
- public void BeginFigure(double x, double y, double? z, double? m)
- {
- if (m_insidePolygon)
- {
- m_figure.Clear();
- m_figure.Add(new Vertex(x, y, z, m));
- }
- else
- {
- m_sink.BeginFigure(x, y, z, m);
- }
- }
-
- public void AddLine(double x, double y, double? z, double? m)
- {
- if (m_insidePolygon)
- {
- m_figure.Add(new Vertex(x, y, z, m));
- }
- else
- {
- m_sink.AddLine(x, y, z, m);
- }
- }
-
- public void EndFigure()
- {
- if (m_insidePolygon)
- {
- if (!IsThinRing())
- {
- PopulateFigure(m_sink);
- }
- }
- else
- {
- m_sink.EndFigure();
- }
- }
-
- private bool IsThinRing()
- {
- try
- {
- SqlGeometryBuilder b = new SqlGeometryBuilder();
- b.SetSrid(m_srid);
- b.BeginGeometry(OpenGisGeometryType.Polygon);
- PopulateFigure(b);
- b.EndGeometry();
- SqlGeometry poly = b.ConstructedGeometry.MakeValid();
- return poly.STArea().Value < m_tolerance * poly.STLength().Value;
- }
- catch (ArgumentException) { }
- catch (FormatException) { }
- return true;
- }
-
- private void PopulateFigure(IGeometrySink sink)
- {
- m_figure[0].BeginFigure(sink);
- for (int i = 1; i < m_figure.Count; i++)
- m_figure[i].AddLine(sink);
- sink.EndFigure();
- }
- }
-}
\ No newline at end of file
diff --git a/Sinks/Projector.cs b/Sinks/Projector.cs
deleted file mode 100644
index 57b0454..0000000
--- a/Sinks/Projector.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//------------------------------------------------------------------------------
-using System;
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- public sealed class Projector : IGeographySink
- {
- private readonly SqlProjection _projection;
- private readonly IGeometrySink _sink;
-
- public Projector(SqlProjection projection, IGeometrySink sink)
- {
- _projection = projection;
- _sink = sink;
- }
-
- public void BeginGeography(OpenGisGeographyType type)
- {
- _sink.BeginGeometry((OpenGisGeometryType)type);
- }
-
- public void EndGeography()
- {
- _sink.EndGeometry();
- }
-
- public void BeginFigure(double latitude, double longitude, Nullable z, Nullable m)
- {
- double x, y;
- _projection.ProjectPoint(latitude, longitude, out x, out y);
- _sink.BeginFigure(x, y, z, m);
- }
-
- public void AddLine(double latitude, double longitude, Nullable z, Nullable m)
- {
- double x, y;
- _projection.ProjectPoint(latitude, longitude, out x, out y);
- _sink.AddLine(x, y, z, m);
- }
-
- public void EndFigure()
- {
- _sink.EndFigure();
- }
-
- public void SetSrid(int srid)
- {
- _sink.SetSrid(srid);
- }
- }
-}
\ No newline at end of file
diff --git a/Sinks/Unprojector.cs b/Sinks/Unprojector.cs
deleted file mode 100644
index 2a78cad..0000000
--- a/Sinks/Unprojector.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//------------------------------------------------------------------------------
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- public sealed class Unprojector : IGeometrySink
- {
- private readonly SqlProjection _projection;
- private readonly IGeographySink _sink;
-
- public Unprojector(SqlProjection projection, IGeographySink sink, int newSrid)
- {
- _projection = projection;
- _sink = sink;
- _sink.SetSrid(newSrid);
- }
-
- public void BeginGeometry(OpenGisGeometryType type)
- {
- _sink.BeginGeography((OpenGisGeographyType)type);
- }
-
- public void EndGeometry()
- {
- _sink.EndGeography();
- }
-
- public void BeginFigure(double x, double y, double? z, double? m)
- {
- double latitude, longitude;
- _projection.UnprojectPoint(x, y, out latitude, out longitude);
- _sink.BeginFigure(latitude, longitude, z, m);
- }
-
- public void AddLine(double x, double y, double? z, double? m)
- {
- double latitude, longitude;
- _projection.UnprojectPoint(x, y, out latitude, out longitude);
- _sink.AddLine(latitude, longitude, z, m);
- }
-
- public void EndFigure()
- {
- _sink.EndFigure();
- }
-
- public void SetSrid(int srid)
- {
- // Input argument not used since a new srid is defined in the constructor.
- }
- }
-}
\ No newline at end of file
diff --git a/Sinks/VacuousGeographyToGeometrySink.cs b/Sinks/VacuousGeographyToGeometrySink.cs
deleted file mode 100644
index a992220..0000000
--- a/Sinks/VacuousGeographyToGeometrySink.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- /**
- * This class implements a completely trivial conversion from geography to geometry, simply taking each
- * point (lat,long) --> (y, x). The class takes a target geometry sink, as well as the target SRID to
- * assign to the results.
- */
- public class VacuousGeographyToGeometrySink : IGeographySink
- {
- private readonly IGeometrySink _target;
- private readonly int _targetSrid;
-
- public VacuousGeographyToGeometrySink(int targetSrid, IGeometrySink target)
- {
- _target = target;
- _targetSrid = targetSrid;
- }
-
- public void AddLine(double latitude, double longitude, double? z, double? m)
- {
- _target.AddLine(longitude, latitude, z, m);
- }
-
- public void BeginFigure(double latitude, double longitude, double? z, double? m)
- {
- _target.BeginFigure(longitude, latitude, z, m);
- }
-
- public void BeginGeography(OpenGisGeographyType type)
- {
- // Convert geography to geometry types...
- _target.BeginGeometry((OpenGisGeometryType) type);
- }
-
- public void EndFigure()
- {
- _target.EndFigure();
- }
-
- public void EndGeography()
- {
- _target.EndGeometry();
- }
-
- public void SetSrid(int srid)
- {
- _target.SetSrid(_targetSrid);
- }
- }
-}
diff --git a/Sinks/VacuousGeometryToGeographySink.cs b/Sinks/VacuousGeometryToGeographySink.cs
deleted file mode 100644
index 597c915..0000000
--- a/Sinks/VacuousGeometryToGeographySink.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- /**
- * This class implements a completely trivial conversion from geometry to geography, simply taking each
- * point (x, y) --> (long, lat). The class takes a target geography sink, as well as the target SRID to
- * assign to the results.
- */
- public class VacuousGeometryToGeographySink : IGeometrySink
- {
- private readonly IGeographySink _target;
- private readonly int _targetSrid;
-
- public VacuousGeometryToGeographySink(int targetSrid, IGeographySink target)
- {
- _target = target;
- _targetSrid = targetSrid;
- }
-
- public void AddLine(double x, double y, double? z, double? m)
- {
- _target.AddLine(y, x, z, m);
- }
-
- public void BeginFigure(double x, double y, double? z, double? m)
- {
- _target.BeginFigure(y, x, z, m);
- }
-
- public void BeginGeometry(OpenGisGeometryType type)
- {
- // Convert geography to geometry types...
- _target.BeginGeography((OpenGisGeographyType) type);
- }
-
- public void EndFigure()
- {
- _target.EndFigure();
- }
-
- public void EndGeometry()
- {
- _target.EndGeography();
- }
-
- public void SetSrid(int srid)
- {
- _target.SetSrid(_targetSrid);
- }
- }
-}
diff --git a/SpatialTools.pfx b/SpatialTools.pfx
new file mode 100644
index 0000000..4a6c71d
Binary files /dev/null and b/SpatialTools.pfx differ
diff --git a/Types/AffineTransform.cs b/Types/AffineTransform.cs
deleted file mode 100644
index 737f7f7..0000000
--- a/Types/AffineTransform.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//------------------------------------------------------------------------------
-
-using System;
-using System.IO;
-using System.Data.SqlTypes;
-using Microsoft.SqlServer.Server;
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- [Serializable]
- [SqlUserDefinedType(Format.UserDefined, IsByteOrdered = false, MaxByteSize = -1, IsFixedLength = false)]
- public sealed class AffineTransform : INullable, IBinarySerialize
- {
- public double ax, bx, cx;
- public double ay, by, cy;
-
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public static AffineTransform Translate(double dx, double dy)
- {
- AffineTransform t = new AffineTransform();
- t.ax = 1;
- t.by = 1;
- t.cx = dx;
- t.cy = dy;
- return t;
- }
-
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public static AffineTransform Rotate(double angleDeg)
- {
- double angle = Util.ToRadians(angleDeg);
- AffineTransform t = new AffineTransform();
- t.ax = Math.Cos(angle);
- t.ay = Math.Sin(angle);
- t.bx = -t.ay;
- t.by = t.ax;
- return t;
- }
-
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public static AffineTransform Scale(double sx, double sy)
- {
- AffineTransform t = new AffineTransform();
- t.ax = sx;
- t.by = sy;
- return t;
- }
-
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public SqlGeometry Apply(SqlGeometry geometry)
- {
- SqlGeometryBuilder builder = new SqlGeometryBuilder();
- geometry.Populate(new GeometryTransformer(builder, this));
- return builder.ConstructedGeometry;
- }
-
- public double GetX(double x, double y)
- {
- return ax * x + bx * y + cx;
- }
-
- public double GetY(double x, double y)
- {
- return ay * x + by * y + cy;
- }
-
- public void Read(BinaryReader r)
- {
- ax = r.ReadDouble();
- bx = r.ReadDouble();
- cx = r.ReadDouble();
- ay = r.ReadDouble();
- by = r.ReadDouble();
- cy = r.ReadDouble();
- }
-
- public void Write(BinaryWriter w)
- {
- w.Write(ax);
- w.Write(bx);
- w.Write(cx);
- w.Write(ay);
- w.Write(by);
- w.Write(cy);
- }
-
- public bool IsNull
- {
- get { return false; }
- }
-
- public static AffineTransform Null
- {
- [SqlMethod(IsDeterministic = true, IsPrecise = true)]
- get
- {
- return null;
- }
- }
-
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public static AffineTransform Parse(SqlString str)
- {
- AffineTransform tranform = new AffineTransform();
-
- string[] args = str.ToString().Split(' ');
- tranform.ax = Double.Parse(args[0]);
- tranform.bx = Double.Parse(args[1]);
- tranform.cx = Double.Parse(args[2]);
- tranform.ay = Double.Parse(args[3]);
- tranform.by = Double.Parse(args[4]);
- tranform.cy = Double.Parse(args[5]);
-
- return tranform;
- }
-
- [return: SqlFacet(MaxSize = -1)]
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public override string ToString()
- {
- return ax + " " + bx + " " + cx + " " + ay + " " + by + " " + cy;
- }
- }
-}
\ No newline at end of file
diff --git a/Types/SqlProjection.cs b/Types/SqlProjection.cs
deleted file mode 100644
index 7b79c1c..0000000
--- a/Types/SqlProjection.cs
+++ /dev/null
@@ -1,284 +0,0 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//------------------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-using System.Data.SqlTypes;
-using System.Diagnostics;
-using System.Globalization;
-using System.IO;
-using System.Reflection;
-using Microsoft.SqlServer.Server;
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- [Serializable]
- [SqlUserDefinedType(Format.UserDefined, IsByteOrdered = false, MaxByteSize = -1, IsFixedLength = false)]
- public sealed class SqlProjection : INullable, IBinarySerialize
- {
- private Projection _projection;
-
- public SqlProjection()
- {
- }
-
- internal SqlProjection(Projection projection)
- {
- Debug.Assert(projection != null);
- _projection = projection;
- }
-
- public static SqlProjection Null
- {
- [SqlMethod(IsDeterministic = true, IsPrecise = true)]
- get
- {
- return new SqlProjection();
- }
- }
-
- // TODO use WKT projection description format
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public static SqlProjection Parse(SqlString s)
- {
- if (s.Value.Equals("NULL"))
- {
- return SqlProjection.Null;
- }
- else
- {
- string[] a = s.Value.Split(' ');
- ConstructorInfo cons = Type.GetType(a[0]).GetConstructor(new Type[] { typeof(Dictionary) });
- return new SqlProjection((Projection)cons.Invoke(new object[] { Projection.ParseParameters(a[1]) }));
- }
- }
-
- // TODO use WKT projection description format
- [return: SqlFacet(MaxSize = -1)]
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public override string ToString()
- {
- if (_projection == null)
- {
- return "NULL";
- }
- else
- {
- return _projection.GetType().FullName + " " + _projection.Parameters;
- }
- }
-
- public bool IsNull
- {
- [SqlMethod(IsDeterministic = true, IsPrecise = true)]
- get { return _projection == null; }
- }
-
- public void Read(BinaryReader r)
- {
- if (r != null)
- {
- string name = r.ReadString();
- if (name.Equals(""))
- {
- _projection = null;
- }
- else
- {
- ConstructorInfo cons = Type.GetType(name).GetConstructor(new Type[] { typeof(Dictionary) });
- _projection = (Projection)cons.Invoke(new object[] { Projection.ParseParameters(r.ReadString()) });
- }
- }
- }
-
- public void Write(BinaryWriter w)
- {
- if (w != null)
- {
- if (_projection == null)
- {
- w.Write("");
- }
- else
- {
- w.Write(_projection.GetType().FullName);
- w.Write(_projection.Parameters);
- }
- }
- }
-
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public static SqlProjection AlbersEqualArea(double longitude0, double latitude0, double parallel1, double parallel2)
- {
- Dictionary parameters = new Dictionary();
- parameters["longitude0"] = longitude0;
- parameters["latitude0"] = latitude0;
- parameters["parallel1"] = parallel1;
- parameters["parallel2"] = parallel2;
- return new SqlProjection(new AlbersEqualAreaProjection(parameters));
- }
-
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public static SqlProjection Equirectangular(double longitude0, double parallel)
- {
- Dictionary parameters = new Dictionary();
- parameters["longitude0"] = longitude0;
- parameters["parallel"] = parallel;
- return new SqlProjection(new EquirectangularProjection(parameters));
- }
-
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public static SqlProjection LambertConformalConic(double longitude0, double latitude, double fi1, double fi2)
- {
- Dictionary parameters = new Dictionary();
- parameters["longitude0"] = longitude0;
- parameters["latitude0"] = latitude;
- parameters["fi1"] = fi1;
- parameters["fi2"] = fi2;
- return new SqlProjection(new LambertConformalConicProjection(parameters));
- }
-
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public static SqlProjection Mercator(double longitude0)
- {
- Dictionary parameters = new Dictionary();
- parameters["longitude0"] = longitude0;
- return new SqlProjection(new MercatorProjection(parameters));
- }
-
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public static SqlProjection ObliqueMercator(double longitude0, double fi1, double lambda1, double fi2, double lambda2)
- {
- Dictionary parameters = new Dictionary();
- parameters["longitude0"] = longitude0;
- parameters["fi1"] = fi1;
- parameters["lambda1"] = lambda1;
- parameters["fi2"] = fi2;
- parameters["lambda2"] = lambda2;
- return new SqlProjection(new ObliqueMercatorProjection(parameters));
- }
-
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public static SqlProjection TranverseMercator(double longitude0)
- {
- Dictionary parameters = new Dictionary();
- parameters["longitude0"] = longitude0;
- return new SqlProjection(new TranverseMercatorProjection(parameters));
- }
-
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public static SqlProjection Gnomonic(double longitude, double latitude)
- {
- Dictionary parameters = new Dictionary();
- parameters["longitude0"] = 0;
- parameters["longitude1"] = longitude;
- parameters["latitude1"] = latitude;
- return new SqlProjection(new GnommonicProjection(parameters));
- }
-
- private static void ThrowIfArgumentNull(INullable argument, string name)
- {
- if (argument == null || argument.IsNull)
- {
- throw new ArgumentNullException(name);
- }
- }
-
- // INSTANCE METHODS AND FIELDS
-
- // Projects geography onto geometry.
- // Returns valid projected SqlGeometry object.
- // Constructed geometry will have the same SRID as geography.
- //
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public SqlGeometry Project(SqlGeography geography)
- {
- ThrowIfArgumentNull(geography, "geography");
- SqlGeometryBuilder builder = new SqlGeometryBuilder();
- geography.Populate(new Projector(this, builder));
- return builder.ConstructedGeometry;
- }
-
- // Uprojects geometry producing geography.
- // Returns a valid uprojected SqlGeography object.
- //
- // SRID taken from the geometry object may not be valid for geography,
- // in which case a new SRID must be given.
- //
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public SqlGeography UnprojectWithSRID(SqlGeometry geometry, SqlInt32 newSrid)
- {
- ThrowIfArgumentNull(geometry, "geometry");
- ThrowIfArgumentNull(newSrid, "newSrid");
- SqlGeographyBuilder builder = new SqlGeographyBuilder();
- geometry.Populate(new Unprojector(this, builder, newSrid.Value));
- return builder.ConstructedGeography;
- }
-
- // Uprojects geometry producing geography.
- // This method assumes that the SRID of geometry object is valid for geography.
- // Returns a valid uprojected SqlGeography object.
- //
- [SqlMethod(IsDeterministic = true, IsPrecise = false)]
- public SqlGeography Unproject(SqlGeometry geometry)
- {
- ThrowIfArgumentNull(geometry, "geometry");
- SqlGeographyBuilder builder = new SqlGeographyBuilder();
- geometry.Populate(new Unprojector(this, builder, geometry.STSrid.Value)); // NOTE srid will be reused
- return builder.ConstructedGeography;
- }
-
- // Longitude and latitude are in degrees.
- // Input Latitude must be in range [-90, 90].
- // Input Longitude must be in range [-15069, 15069].
- //
- public void ProjectPoint(double latitudeDeg, double longitudeDeg, out double x, out double y)
- {
- double latitude = MathX.InputLat(latitudeDeg, 90, "latitude");
- double longitude = MathX.NormalizeLongitudeRad(MathX.InputLong(longitudeDeg, 15069, "longitude") - _projection.CentralLongitudeRad);
-
- _projection.Project(latitude, longitude, out x, out y);
-
- if (Double.IsNaN(x))
- {
- throw new ArgumentOutOfRangeException("x");
- }
- if (Double.IsNaN(y))
- {
- throw new ArgumentOutOfRangeException("y");
- }
- }
-
- // Longitude and latitude are in degrees.
- // Output Latitude will be in range [-90, 90].
- // Output Longitude will be in range [-180, 180].
- //
- public void UnprojectPoint(double x, double y, out double latitudeDeg, out double longitudeDeg)
- {
- if (Double.IsNaN(x))
- {
- throw new ArgumentException(Resource.InputCoordinateIsNaN, "x");
- }
- if (Double.IsNaN(y))
- {
- throw new ArgumentException(Resource.InputCoordinateIsNaN, "y");
- }
-
- double latitude, longitude;
- _projection.Unproject(x, y, out latitude, out longitude);
-
- if (Double.IsNaN(latitude) || latitude < -Math.PI / 2 || latitude > Math.PI / 2)
- {
- throw new ArgumentOutOfRangeException("latitude", String.Format(CultureInfo.InvariantCulture, Resource.OutputLatitudeIsOutOfRange, latitude));
- }
- if (Double.IsNaN(longitude) || longitude < -Math.PI || longitude > Math.PI)
- {
- throw new ArgumentOutOfRangeException("longitude", String.Format(CultureInfo.InvariantCulture, Resource.OutputLongitudeIsOutOfRange, longitude));
- }
-
- latitudeDeg = MathX.Clamp(90, Util.ToDegrees(latitude));
- longitudeDeg = MathX.NormalizeLongitudeDeg(Util.ToDegrees(longitude + _projection.CentralLongitudeRad));
- }
- }
-}
\ No newline at end of file
diff --git a/Aggregates/GeographyUnionAggregate.cs b/src/Aggregates/GeographyUnionAggregate.cs
similarity index 77%
rename from Aggregates/GeographyUnionAggregate.cs
rename to src/Aggregates/GeographyUnionAggregate.cs
index 9693265..3129240 100644
--- a/Aggregates/GeographyUnionAggregate.cs
+++ b/src/Aggregates/GeographyUnionAggregate.cs
@@ -1,303 +1,313 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//------------------------------------------------------------------------------
-
-using System;
-using System.IO;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using Microsoft.SqlServer.Server;
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- [Serializable]
- [StructLayout(LayoutKind.Sequential)]
- [SqlUserDefinedAggregate(
- Format.UserDefined,
- IsInvariantToDuplicates = true,
- IsInvariantToNulls = true,
- IsInvariantToOrder = true,
- IsNullIfEmpty = false,
- MaxByteSize = -1)]
- public class GeographyUnionAggregate : IBinarySerialize
- {
- private GeographyCollectionAggregate aggregate;
-
- public void Init()
- {
- if (aggregate == null) aggregate = new GeographyCollectionAggregate();
- aggregate.Init();
- }
-
- public void Accumulate(SqlGeography g)
- {
- aggregate.Accumulate(g);
- }
-
- public void Merge(GeographyUnionAggregate group)
- {
- aggregate.Merge(group.aggregate);
- }
-
- public SqlGeography Terminate()
- {
- SqlGeography g = aggregate.Terminate();
-
- if (g.IsNull || g.STIsEmpty().Value)
- return g;
-
- try
- {
- // Force self union of the collection.
- return g.STUnion(g.STPointN(1));
- }
- catch (ArgumentException)
- {
- // Union of an object with its first point could fail is the object
- // is slightly smaller than a hemisphere. So, do the more expensive,
- // but correct union here.
- return g.STUnion(g);
- }
- }
-
- public void Read(BinaryReader r)
- {
- if (aggregate == null) aggregate = new GeographyCollectionAggregate();
- aggregate.Read(r);
- }
-
- public void Write(BinaryWriter w)
- {
- aggregate.Write(w);
- }
- }
-
- [Serializable]
- [StructLayout(LayoutKind.Sequential)]
- [SqlUserDefinedAggregate(
- Format.UserDefined,
- IsInvariantToDuplicates = true,
- IsInvariantToNulls = true,
- IsInvariantToOrder = true,
- IsNullIfEmpty = false,
- MaxByteSize = -1)]
- public class GeographyCollectionAggregate : IBinarySerialize
- {
- private SqlGeographyBuilder m_builder;
- private IGeographySink m_sink;
- private int m_srid;
- private bool m_error;
-
- public void Init()
- {
- m_srid = -1;
- m_builder = null;
- m_sink = null;
- m_error = false;
- }
-
- private bool IsInitialState()
- {
- return m_builder == null && !m_error;
- }
-
- public void Accumulate(SqlGeography g)
- {
- if (g == null || g.IsNull || m_error) return;
- Reset(g.STSrid.Value);
- if (m_sink != null) g.Populate(m_sink);
- }
-
- private void Reset(int srid)
- {
- if (m_builder == null)
- {
- m_srid = srid;
- m_builder = new SqlGeographyBuilder();
- m_builder.SetSrid(m_srid);
- m_builder.BeginGeography(OpenGisGeographyType.GeometryCollection);
- m_sink = new StripSRID(m_builder);
- }
- else if (srid != m_srid)
- {
- m_srid = -1;
- m_builder = null;
- m_sink = null;
- m_error = true;
- }
- }
-
- private SqlGeography ConstructedGeography()
- {
- SqlGeography g = SqlGeography.Null;
- if (!m_error)
- {
- try
- {
- m_builder.EndGeography();
- g = m_builder.ConstructedGeography;
- }
- catch (ArgumentException)
- {
- // Result is larger than a hemisphere!
- }
- }
- Init();
- return g;
- }
-
- public void Merge(GeographyCollectionAggregate group)
- {
- if (group.IsInitialState()) return;
-
- SqlGeography g = group.ConstructedGeography();
- if (g.IsNull)
- {
- m_srid = -1;
- m_builder = null;
- m_sink = null;
- m_error = true;
- }
- else
- {
- Reset(g.STSrid.Value);
- g.Populate(new StripCollection(m_builder));
- }
- }
-
- public SqlGeography Terminate()
- {
- if (IsInitialState()) return SqlGeography.Null;
-
- SqlGeography g = ConstructedGeography();
- Init();
- return g;
- }
-
- public void Read(BinaryReader r)
- {
- if (r.ReadBoolean())
- {
- Init();
- }
- else
- {
- SqlGeography g = new SqlGeography();
- g.Read(r);
- if (g.IsNull)
- {
- m_srid = -1;
- m_error = true;
- m_builder = null;
- m_sink = null;
- }
- else
- {
- m_srid = g.STSrid.Value;
- m_builder = new SqlGeographyBuilder();
- m_sink = new StripSRID(m_builder);
- m_builder.SetSrid(m_srid);
- m_builder.BeginGeography(OpenGisGeographyType.GeometryCollection);
- g.Populate(new StripCollection(m_builder));
- }
- }
- }
-
- public void Write(BinaryWriter w)
- {
- w.Write(IsInitialState());
- if (!IsInitialState())
- {
- ConstructedGeography().Write(w);
- Init();
- }
- }
- }
-
- internal class StripSRID : IGeographySink
- {
- private readonly IGeographySink m_sink;
-
- public StripSRID(IGeographySink sink)
- {
- m_sink = sink;
- }
-
- public void BeginGeography(OpenGisGeographyType type)
- {
- m_sink.BeginGeography(type);
- }
-
- public void BeginFigure(double latitude, double longitude, double? z, double? m)
- {
- m_sink.BeginFigure(latitude, longitude, z, m);
- }
-
- public void AddLine(double latitude, double longitude, double? z, double? m)
- {
- m_sink.AddLine(latitude, longitude, z, m);
- }
-
- public void EndFigure()
- {
- m_sink.EndFigure();
- }
-
- public void EndGeography()
- {
- m_sink.EndGeography();
- }
-
- public void SetSrid(int srid) { }
- }
-
- internal class StripCollection : IGeographySink
- {
- private readonly IGeographySink m_sink;
- private int m_depth;
-
- public StripCollection(IGeographySink sink)
- {
- m_sink = sink;
- }
-
- public void BeginGeography(OpenGisGeographyType type)
- {
- if (m_depth > 0)
- {
- m_sink.BeginGeography(type);
- }
- else
- {
- Debug.Assert(OpenGisGeographyType.GeometryCollection == type);
- }
- m_depth += 1;
- }
-
- public void BeginFigure(double latitude, double longitude, double? z, double? m)
- {
- m_sink.BeginFigure(latitude, longitude, z, m);
- }
-
- public void AddLine(double latitude, double longitude, double? z, double? m)
- {
- m_sink.AddLine(latitude, longitude, z, m);
- }
-
- public void EndFigure()
- {
- m_sink.EndFigure();
- }
-
- public void EndGeography()
- {
- m_depth -= 1;
- if (m_depth > 0) m_sink.EndGeography();
- }
-
- public void SetSrid(int srid)
- {
- }
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.IO;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Microsoft.SqlServer.Server;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.Aggregates
+{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ [SqlUserDefinedAggregate(
+ Format.UserDefined,
+ IsInvariantToDuplicates = true,
+ IsInvariantToNulls = true,
+ IsInvariantToOrder = true,
+ IsNullIfEmpty = false,
+ MaxByteSize = -1)]
+ public class GeographyUnionAggregate : IBinarySerialize
+ {
+ private GeographyCollectionAggregate aggregate;
+
+ public void Init()
+ {
+ if (aggregate == null) aggregate = new GeographyCollectionAggregate();
+ aggregate.Init();
+ }
+
+ public void Accumulate(SqlGeography g)
+ {
+ aggregate.Accumulate(g);
+ }
+
+ public void Merge(GeographyUnionAggregate group)
+ {
+ aggregate.Merge(group.aggregate);
+ }
+
+ public SqlGeography Terminate()
+ {
+ var g = aggregate.Terminate();
+
+ if (g.IsNull || g.STIsEmpty().Value)
+ return g;
+
+ try
+ {
+ // Force self union of the collection.
+ return g.STUnion(g.STPointN(1));
+ }
+ catch (ArgumentException)
+ {
+ // Union of an object with its first point could fail is the object
+ // is slightly smaller than a hemisphere. So, do the more expensive,
+ // but correct union here.
+ return g.STUnion(g);
+ }
+ }
+
+ public void Read(BinaryReader r)
+ {
+ if (aggregate == null) aggregate = new GeographyCollectionAggregate();
+ aggregate.Read(r);
+ }
+
+ public void Write(BinaryWriter w)
+ {
+ aggregate.Write(w);
+ }
+ }
+
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ [SqlUserDefinedAggregate(
+ Format.UserDefined,
+ IsInvariantToDuplicates = true,
+ IsInvariantToNulls = true,
+ IsInvariantToOrder = true,
+ IsNullIfEmpty = false,
+ MaxByteSize = -1)]
+ public class GeographyCollectionAggregate : IBinarySerialize
+ {
+ private SqlGeographyBuilder m_builder;
+ private IGeographySink110 m_sink;
+ private int m_srid;
+ private bool m_error;
+
+ public void Init()
+ {
+ m_srid = -1;
+ m_builder = null;
+ m_sink = null;
+ m_error = false;
+ }
+
+ private bool IsInitialState()
+ {
+ return m_builder == null && !m_error;
+ }
+
+ public void Accumulate(SqlGeography g)
+ {
+ if (g == null || g.IsNull || m_error) return;
+ Reset(g.STSrid.Value);
+ if (m_sink != null) g.Populate(m_sink);
+ }
+
+ private void Reset(int srid)
+ {
+ if (m_builder == null)
+ {
+ m_srid = srid;
+ m_builder = new SqlGeographyBuilder();
+ m_builder.SetSrid(m_srid);
+ m_builder.BeginGeography(OpenGisGeographyType.GeometryCollection);
+ m_sink = new StripSRID(m_builder);
+ }
+ else if (srid != m_srid)
+ {
+ m_srid = -1;
+ m_builder = null;
+ m_sink = null;
+ m_error = true;
+ }
+ }
+
+ private SqlGeography ConstructedGeography()
+ {
+ SqlGeography g = SqlGeography.Null;
+ if (!m_error)
+ {
+ try
+ {
+ m_builder.EndGeography();
+ g = m_builder.ConstructedGeography;
+ }
+ catch (ArgumentException)
+ {
+ // Result is larger than a hemisphere!
+ }
+ }
+ Init();
+ return g;
+ }
+
+ public void Merge(GeographyCollectionAggregate group)
+ {
+ if (group.IsInitialState()) return;
+
+ SqlGeography g = group.ConstructedGeography();
+ if (g.IsNull)
+ {
+ m_srid = -1;
+ m_builder = null;
+ m_sink = null;
+ m_error = true;
+ }
+ else
+ {
+ Reset(g.STSrid.Value);
+ g.Populate(new StripCollection(m_builder));
+ }
+ }
+
+ public SqlGeography Terminate()
+ {
+ if (IsInitialState()) return SqlGeography.Null;
+
+ SqlGeography g = ConstructedGeography();
+ Init();
+ return g;
+ }
+
+ public void Read(BinaryReader r)
+ {
+ if (r.ReadBoolean())
+ {
+ Init();
+ }
+ else
+ {
+ SqlGeography g = new SqlGeography();
+ g.Read(r);
+ if (g.IsNull)
+ {
+ m_srid = -1;
+ m_error = true;
+ m_builder = null;
+ m_sink = null;
+ }
+ else
+ {
+ m_srid = g.STSrid.Value;
+ m_builder = new SqlGeographyBuilder();
+ m_sink = new StripSRID(m_builder);
+ m_builder.SetSrid(m_srid);
+ m_builder.BeginGeography(OpenGisGeographyType.GeometryCollection);
+ g.Populate(new StripCollection(m_builder));
+ }
+ }
+ }
+
+ public void Write(BinaryWriter w)
+ {
+ w.Write(IsInitialState());
+ if (!IsInitialState())
+ {
+ ConstructedGeography().Write(w);
+ Init();
+ }
+ }
+ }
+
+ internal class StripSRID : IGeographySink110
+ {
+ private readonly IGeographySink110 _sink;
+
+ public StripSRID(IGeographySink110 sink)
+ {
+ _sink = sink;
+ }
+
+ public void BeginGeography(OpenGisGeographyType type)
+ {
+ _sink.BeginGeography(type);
+ }
+
+ public void BeginFigure(double latitude, double longitude, double? z, double? m)
+ {
+ _sink.BeginFigure(latitude, longitude, z, m);
+ }
+
+ public void AddLine(double latitude, double longitude, double? z, double? m)
+ {
+ _sink.AddLine(latitude, longitude, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ _sink.EndFigure();
+ }
+
+ public void EndGeography()
+ {
+ _sink.EndGeography();
+ }
+
+ public void SetSrid(int srid) { }
+ }
+
+ internal class StripCollection : IGeographySink110
+ {
+ private readonly IGeographySink110 _sink;
+ private int _depth;
+
+ public StripCollection(IGeographySink110 sink)
+ {
+ _sink = sink;
+ }
+
+ public void BeginGeography(OpenGisGeographyType type)
+ {
+ if (_depth > 0)
+ {
+ _sink.BeginGeography(type);
+ }
+ else
+ {
+ Debug.Assert(OpenGisGeographyType.GeometryCollection == type);
+ }
+ _depth += 1;
+ }
+
+ public void BeginFigure(double latitude, double longitude, double? z, double? m)
+ {
+ _sink.BeginFigure(latitude, longitude, z, m);
+ }
+
+ public void AddLine(double latitude, double longitude, double? z, double? m)
+ {
+ _sink.AddLine(latitude, longitude, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ _sink.EndFigure();
+ }
+
+ public void EndGeography()
+ {
+ _depth -= 1;
+ if (_depth > 0) _sink.EndGeography();
+ }
+
+ public void SetSrid(int srid)
+ {
+ }
+ }
}
\ No newline at end of file
diff --git a/Aggregates/GeometryEnvelopeAggregate.cs b/src/Aggregates/GeometryEnvelopeAggregate.cs
similarity index 64%
rename from Aggregates/GeometryEnvelopeAggregate.cs
rename to src/Aggregates/GeometryEnvelopeAggregate.cs
index 0c2bb91..5b60903 100644
--- a/Aggregates/GeometryEnvelopeAggregate.cs
+++ b/src/Aggregates/GeometryEnvelopeAggregate.cs
@@ -1,101 +1,106 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//------------------------------------------------------------------------------
-
-using System;
-using System.Runtime.InteropServices;
-using Microsoft.SqlServer.Server;
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- [Serializable]
- [StructLayout(LayoutKind.Sequential)]
- [SqlUserDefinedAggregate(
- Format.Native,
- IsInvariantToDuplicates = true,
- IsInvariantToNulls = true,
- IsInvariantToOrder = true,
- IsNullIfEmpty = true)]
- public class GeometryEnvelopeAggregate : IGeometrySink
- {
- private double minX, maxX, minY, maxY;
- private int lastSrid;
- private bool failed;
-
- public void SetSrid(int srid)
- {
- if (lastSrid != -1 && lastSrid != srid) failed = true;
- lastSrid = srid;
- }
-
- public void BeginGeometry(OpenGisGeometryType type) { }
-
- public void BeginFigure(double x, double y, double? z, double? m)
- {
- IncludePoint(x, y);
- }
-
- public void AddLine(double x, double y, double? z, double? m)
- {
- IncludePoint(x, y);
- }
-
- public void EndFigure() { }
-
- public void EndGeometry() { }
-
- public void Init()
- {
- minX = minY = Double.PositiveInfinity;
- maxX = maxY = Double.NegativeInfinity;
- lastSrid = -1;
- failed = false;
- }
-
- public void Accumulate(SqlGeometry geometry)
- {
- if (geometry != null) geometry.Populate(this);
- }
-
- public void Merge(GeometryEnvelopeAggregate group)
- {
- minX = Math.Min(minX, group.minX);
- maxX = Math.Max(maxX, group.maxX);
- minY = Math.Min(minY, group.minY);
- maxY = Math.Max(maxY, group.maxY);
-
- if (group.lastSrid != -1)
- {
- if (lastSrid != -1 && lastSrid != group.lastSrid) failed = true;
- lastSrid = group.lastSrid;
- }
- if (group.failed) failed = true;
- }
-
- public SqlGeometry Terminate()
- {
- if (failed) return SqlGeometry.Null;
-
- SqlGeometryBuilder b = new SqlGeometryBuilder();
- b.SetSrid(lastSrid);
- b.BeginGeometry(OpenGisGeometryType.Polygon);
- b.BeginFigure(minX, minY);
- b.AddLine(maxX, minY);
- b.AddLine(maxX, maxY);
- b.AddLine(minX, maxY);
- b.AddLine(minX, minY);
- b.EndFigure();
- b.EndGeometry();
- return b.ConstructedGeometry;
- }
-
- private void IncludePoint(double x, double y)
- {
- minX = Math.Min(minX, x);
- maxX = Math.Max(maxX, x);
- minY = Math.Min(minY, y);
- maxY = Math.Max(maxY, y);
- }
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Runtime.InteropServices;
+using Microsoft.SqlServer.Server;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.Aggregates
+{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ [SqlUserDefinedAggregate(
+ Format.Native,
+ IsInvariantToDuplicates = true,
+ IsInvariantToNulls = true,
+ IsInvariantToOrder = true,
+ IsNullIfEmpty = true)]
+ public class GeometryEnvelopeAggregate : IGeometrySink110
+ {
+ private double minX, maxX, minY, maxY;
+ private int lastSrid;
+ private bool failed;
+
+ public void SetSrid(int srid)
+ {
+ if (lastSrid != -1 && lastSrid != srid) failed = true;
+ lastSrid = srid;
+ }
+
+ public void BeginGeometry(OpenGisGeometryType type) { }
+
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ IncludePoint(x, y);
+ }
+
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ IncludePoint(x, y);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure() { }
+
+ public void EndGeometry() { }
+
+ public void Init()
+ {
+ minX = minY = double.PositiveInfinity;
+ maxX = maxY = double.NegativeInfinity;
+ lastSrid = -1;
+ failed = false;
+ }
+
+ public void Accumulate(SqlGeometry geometry)
+ {
+ geometry?.Populate(this);
+ }
+
+ public void Merge(GeometryEnvelopeAggregate group)
+ {
+ minX = Math.Min(minX, group.minX);
+ maxX = Math.Max(maxX, group.maxX);
+ minY = Math.Min(minY, group.minY);
+ maxY = Math.Max(maxY, group.maxY);
+
+ if (group.lastSrid != -1)
+ {
+ if (lastSrid != -1 && lastSrid != group.lastSrid) failed = true;
+ lastSrid = group.lastSrid;
+ }
+ if (group.failed) failed = true;
+ }
+
+ public SqlGeometry Terminate()
+ {
+ if (failed) return SqlGeometry.Null;
+
+ var builder = new SqlGeometryBuilder();
+ builder.SetSrid(lastSrid);
+ builder.BeginGeometry(OpenGisGeometryType.Polygon);
+ builder.BeginFigure(minX, minY);
+ builder.AddLine(maxX, minY);
+ builder.AddLine(maxX, maxY);
+ builder.AddLine(minX, maxY);
+ builder.AddLine(minX, minY);
+ builder.EndFigure();
+ builder.EndGeometry();
+ return builder.ConstructedGeometry;
+ }
+
+ private void IncludePoint(double x, double y)
+ {
+ minX = Math.Min(minX, x);
+ maxX = Math.Max(maxX, x);
+ minY = Math.Min(minY, y);
+ maxY = Math.Max(maxY, y);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Functions/General/Geography.cs b/src/Functions/General/Geography.cs
new file mode 100644
index 0000000..0950236
--- /dev/null
+++ b/src/Functions/General/Geography.cs
@@ -0,0 +1,346 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Data.SqlTypes;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Sinks.Geography;
+using SQLSpatialTools.Sinks.Geometry;
+using SQLSpatialTools.Types.SQL;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Functions.General
+{
+ ///
+ /// This class contains General Geography type functions that can be registered in SQL Server.
+ ///
+ public static class Geography
+ {
+ ///
+ /// Make our LocateAlongGeographySink into a function call.
+ /// This function just hooks up and runs a pipeline using the sink.
+ ///
+ /// Sql Geography
+ /// Distance at which the point to be located
+ ///
+ public static SqlGeography LocatePointAlongGeog(SqlGeography geography, double distance)
+ {
+ var geogBuilder = new SqlGeographyBuilder();
+ var geogSink = new LocateAlongGeographySink(distance, geogBuilder);
+ geography.Populate(geogSink);
+ return geogBuilder.ConstructedGeography;
+ }
+
+ ///
+ /// Find the point that is the given distance from the start point in the direction of the end point.
+ /// The distance must be less than the distance between these two points.
+ ///
+ /// Start Geography Point
+ /// End Geography Point
+ /// Distance at which the point to be located
+ ///
+ public static SqlGeography InterpolateBetweenGeog(SqlGeography start, SqlGeography end, double distance)
+ {
+ // We need to check a few prerequisite.
+ // We only operate on points.
+ if (!start.IsPoint() || !end.IsPoint())
+ {
+ throw new ArgumentException(ErrorMessage.PointCompatible);
+ }
+
+ // The SRIDs also have to match
+ var srid = start.STSrid.Value;
+ if (srid != end.STSrid.Value)
+ {
+ throw new ArgumentException(ErrorMessage.SRIDCompatible);
+ }
+
+ // Finally, the distance has to fall between these points.
+ var length = start.STDistance(end).Value;
+ if (distance > length)
+ {
+ throw new ArgumentException(ErrorMessage.DistanceMustBeBetweenTwoPoints);
+ }
+
+ if (distance < 0)
+ {
+ throw new ArgumentException(ErrorMessage.DistanceMustBePositive);
+ }
+
+ // We'll just do this by binary search---surely this could be more efficient,
+ // but this is relatively easy.
+ //
+ // Note that we can't just take the take the linear combination of end vectors because we
+ // aren't working on a sphere.
+
+ // We are going to do our binary search using 3D Cartesian values, however
+ var startCart = SpatialUtil.GeographicToCartesian(start);
+ var endCart = SpatialUtil.GeographicToCartesian(end);
+
+ SqlGeography current;
+ double currentDistance;
+
+ // Keep refining until we slip below the THRESHOLD value.
+ do
+ {
+ var currentCart = (startCart + endCart) / 2;
+ current = SpatialUtil.CartesianToGeographic(currentCart, srid);
+ currentDistance = start.STDistance(current).Value;
+
+ if (distance <= currentDistance)
+ endCart = currentCart;
+ else
+ startCart = currentCart;
+ } while (Math.Abs(currentDistance - distance) > Constants.Tolerance);
+
+ return current;
+ }
+
+ ///
+ /// This function is used for generating a new geography object where additional points are inserted
+ /// along every line in such a way that the angle between two consecutive points does not
+ /// exceed a prescribed angle. The points are generated between the unit vectors that correspond
+ /// to the line's start and end along the great-circle arc on the unit sphere. This follows the
+ /// definition of geodetic lines in SQL Server.
+ ///
+ /// Input Sql geography
+ /// Max Angle
+ ///
+ public static SqlGeography DensifyGeography(SqlGeography geography, double maxAngle)
+ {
+ var geogBuilder = new SqlGeographyBuilder();
+ geography.Populate(new DensifyGeographySink(geogBuilder, maxAngle));
+ return geogBuilder.ConstructedGeography;
+ }
+
+ ///
+ /// Performs a complete trivial conversion from geography to geometry, simply taking each
+ ///point (lat,long) -> (y, x). The result is assigned the given SRID.
+ ///
+ ///
+ ///
+ ///
+ public static SqlGeometry VacuousGeographyToGeometry(SqlGeography toConvert, int targetSrid)
+ {
+ var geomBuilder = new SqlGeometryBuilder();
+ toConvert.Populate(new VacuousGeographyToGeometrySink(targetSrid, geomBuilder));
+ return geomBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Computes ConvexHull of input geography and returns a polygon (unless all input points are collinear).
+ ///
+ /// Input Sql Geography
+ ///
+ public static SqlGeography ConvexHullGeography(SqlGeography geography)
+ {
+ if (geography.IsNull || geography.STIsEmpty().Value) return geography;
+
+ var center = geography.EnvelopeCenter();
+ var gnomonicProjection = SqlProjection.Gnomonic(center.Long.Value, center.Lat.Value);
+ var geometry = gnomonicProjection.Project(geography);
+ return gnomonicProjection.Unproject(geometry.MakeValid().STConvexHull());
+ }
+
+ ///
+ /// Computes ConvexHull of input WKT and returns a polygon (unless all input points are collinear).
+ /// This function does not require its input to be a valid geography. This function does require
+ /// that the WKT coordinate values are longitude/latitude values, in that order and that a valid
+ /// geography SRID value is supplied.
+ ///
+ /// Input Well Known Text
+ /// Spatial Reference Identifier
+ ///
+ public static SqlGeography ConvexHullGeographyFromText(string inputWKT, int srid)
+ {
+ var geometry = SqlGeometry.STGeomFromText(new SqlChars(inputWKT), srid);
+ var geographyBuilder = new SqlGeographyBuilder();
+ geometry.Populate(new GeometryToPointGeographySink(geographyBuilder));
+ return ConvexHullGeography(geographyBuilder.ConstructedGeography);
+ }
+
+ ///
+ /// Check if an input geometry can represent a valid geography without throwing an exception.
+ /// This function requires that the geometry be in longitude/latitude coordinates and that
+ /// those coordinates are in correct order in the geometry instance (i.e. latitude/longitude
+ /// not longitude/latitude). This function will return false (0) if the input geometry is not
+ /// in the correct latitude/longitude format, including a valid geography SRID.
+ ///
+ /// Input Sql Geometry
+ ///
+ public static bool IsValidGeographyFromGeometry(SqlGeometry geometry)
+ {
+ if (geometry.IsNull) return false;
+
+ try
+ {
+ var geogBuilder = new SqlGeographyBuilder();
+ geometry.Populate(new VacuousGeometryToGeographySink(geometry.STSrid.Value, geogBuilder));
+ // ReSharper disable once UnusedVariable
+ var geography = geogBuilder.ConstructedGeography;
+ return true;
+ }
+ catch (FormatException)
+ {
+ // Syntax error
+ return false;
+ }
+ catch (ArgumentException)
+ {
+ // Semantic (Geometrical) error
+ return false;
+ }
+ }
+
+ ///
+ /// Check if an input WKT can represent a valid geography. This function requires that
+ /// the WTK coordinate values are longitude/latitude values, in that order and that a valid
+ /// geography SRID value is supplied. This function will not throw an exception even in
+ /// edge conditions (i.e. longitude/latitude coordinates are reversed to latitude/longitude).
+ ///
+ /// Input Well Known Text
+ /// Spatial Reference Identifier
+ ///
+ public static bool IsValidGeographyFromText(string inputWKT, int srid)
+ {
+ try
+ {
+ // If parse succeeds then our input is valid
+ inputWKT.GetGeog(srid);
+ return true;
+ }
+ catch (FormatException)
+ {
+ // Syntax error
+ return false;
+ }
+ catch (ArgumentException)
+ {
+ // Semantic (Geometrical) error
+ return false;
+ }
+ }
+
+ ///
+ /// Convert an input geometry instance to a valid geography instance.
+ /// This function requires that the WKT coordinate values are longitude/latitude values,
+ /// in that order and that a valid geography SRID value is supplied.
+ ///
+ /// Input Sql Geometry
+ ///
+ public static SqlGeography MakeValidGeographyFromGeometry(SqlGeometry geometry)
+ {
+ if (geometry.IsNull) return SqlGeography.Null;
+ if (geometry.STIsEmpty().Value) return CreateEmptyGeography(geometry.STSrid.Value);
+
+ // Extract vertices from our input to be able to compute geography EnvelopeCenter
+ var pointSetBuilder = new SqlGeographyBuilder();
+ geometry.Populate(new GeometryToPointGeographySink(pointSetBuilder));
+ SqlGeography center;
+ try
+ {
+ center = pointSetBuilder.ConstructedGeography.EnvelopeCenter();
+ }
+ catch (ArgumentException)
+ {
+ // Input is larger than a hemisphere.
+ return SqlGeography.Null;
+ }
+
+ // Construct Gnomonic projection centered on input geography
+ var gnomonicProjection = SqlProjection.Gnomonic(center.Long.Value, center.Lat.Value);
+
+ // Project, run geometry MakeValid and unproject
+ var geometryBuilder = new SqlGeometryBuilder();
+ geometry.Populate(new VacuousGeometryToGeographySink(geometry.STSrid.Value, new Projector(gnomonicProjection, geometryBuilder)));
+ var outGeometry = Geometry.MakeValidForGeography(geometryBuilder.ConstructedGeometry);
+
+ try
+ {
+ return gnomonicProjection.Unproject(outGeometry);
+ }
+ catch (ArgumentException)
+ {
+ // Try iteratively to reduce the object to remove very close vertices.
+ for (var tolerance = 1e-4; tolerance <= 1e6; tolerance *= 2)
+ {
+ try
+ {
+ return gnomonicProjection.Unproject(outGeometry.Reduce(tolerance));
+ }
+ catch (ArgumentException)
+ {
+ // keep trying
+ }
+ }
+ return SqlGeography.Null;
+ }
+ }
+
+ ///
+ /// Constructs an empty Sql Geography
+ ///
+ /// Spatial Reference Identifier
+ ///
+ private static SqlGeography CreateEmptyGeography(int srid)
+ {
+ var geogBuilder = new SqlGeographyBuilder();
+ geogBuilder.SetSrid(srid);
+ geogBuilder.BeginGeography(OpenGisGeographyType.GeometryCollection);
+ geogBuilder.EndGeography();
+ return geogBuilder.ConstructedGeography;
+ }
+
+ ///
+ /// Convert an input WKT to a valid geography instance.
+ /// This function requires that the WKT coordinate values are longitude/latitude values,
+ /// in that order and that a valid geography SRID value is supplied.
+ ///
+ /// Input Well Know Text
+ /// Spatial Reference Identifier
+ ///
+ public static SqlGeography MakeValidGeographyFromText(string inputWKT, int srid)
+ {
+ return MakeValidGeographyFromGeometry(inputWKT.GetGeom(srid));
+ }
+
+ // Selectively filter unwanted artifacts in input object:
+ // - empty shapes (if [filterEmptyShapes] is true)
+ // - points (if [filterPoints] is true)
+ // - line strings shorter than provided tolerance (if lineString.STLength < [lineStringTolerance])
+ // - polygon rings thinner than provided tolerance (if ring.STArea < ring.STLength * [ringTolerance])
+ // - general behavior: Returned spatial objects will always to the simplest OGC construction
+ //
+ public static SqlGeography FilterArtifactsGeography(SqlGeography geography, bool filterEmptyShapes, bool filterPoints, double lineStringTolerance, double ringTolerance)
+ {
+ if (geography == null || geography.IsNull)
+ return geography;
+
+ var geogBuilder = new SqlGeographyBuilder();
+ IGeographySink110 filter = geogBuilder;
+
+ if (filterEmptyShapes)
+ filter = new GeographyEmptyShapeFilter(filter);
+ if (ringTolerance > 0)
+ filter = new GeographyThinRingFilter(filter, ringTolerance);
+ if (lineStringTolerance > 0)
+ filter = new GeographyShortLineStringFilter(filter, lineStringTolerance);
+ if (filterPoints)
+ filter = new GeographyPointFilter(filter);
+
+ geography.Populate(filter);
+ geography = geogBuilder.ConstructedGeography;
+
+ if (geography == null || geography.IsNull)
+ return geography;
+
+ // Strip collections with single element
+ while (geography.STNumGeometries().Value == 1 && geography.InstanceOf("GEOMETRYCOLLECTION").Value)
+ geography = geography.STGeometryN(1);
+
+ return geography;
+ }
+
+ }
+}
diff --git a/src/Functions/General/Geometry.cs b/src/Functions/General/Geometry.cs
new file mode 100644
index 0000000..dd10c88
--- /dev/null
+++ b/src/Functions/General/Geometry.cs
@@ -0,0 +1,209 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Sinks.Geometry;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Functions.General
+{
+ ///
+ /// This class contains General Geometry type functions that can be registered in SQL Server.
+ ///
+ public static class Geometry
+ {
+ ///
+ /// Selectively filter unwanted artifacts in input object:
+ /// - empty shapes (if [filterEmptyShapes] is true)
+ /// - points (if [filterPoints] is true)
+ /// - line strings shorter than provided tolerance (if lineString.STLength less than lineStringTolerance)
+ /// - polygon rings thinner than provided tolerance (if ring.STArea less than ring.STLength * ringTolerance)
+ /// - general behavior: Returned spatial objects will always to the simplest OGC construction
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static SqlGeometry FilterArtifactsGeometry(SqlGeometry geometry, bool filterEmptyShapes, bool filterPoints, double lineStringTolerance, double ringTolerance)
+ {
+ if (geometry == null || geometry.IsNull)
+ return geometry;
+
+ var geomBuilder = new SqlGeometryBuilder();
+ IGeometrySink110 filter = geomBuilder;
+
+ if (filterEmptyShapes)
+ filter = new GeometryEmptyShapeFilter(filter);
+ if (ringTolerance > 0)
+ filter = new GeometryThinRingFilter(filter, ringTolerance);
+ if (lineStringTolerance > 0)
+ filter = new GeometryShortLineStringFilter(filter, lineStringTolerance);
+ if (filterPoints)
+ filter = new GeometryPointFilter(filter);
+
+ geometry.Populate(filter);
+ geometry = geomBuilder.ConstructedGeometry;
+
+ if (geometry == null || geometry.IsNull || !geometry.STIsValid().Value)
+ return geometry;
+
+ // Strip collections with single element
+ while (geometry.STNumGeometries().Value == 1 && geometry.InstanceOf("GEOMETRYCOLLECTION").Value)
+ geometry = geometry.STGeometryN(1);
+
+ return geometry;
+ }
+
+ ///
+ /// Convert Z co-ordinate of geom from XYZ to XYM
+ ///
+ /// Well Know Text with x,y,z representation
+ /// Spatial Reference Identifier
+ ///
+ public static SqlGeometry GeomFromXYMText(string wktXYM, int srid)
+ {
+ var res = new ConvertXYZ2XYMGeometrySink();
+ var geom = wktXYM.GetGeom(srid);
+ geom.Populate(res);
+ return res.ConstructedGeometry;
+ }
+
+ ///
+ /// Find the point that is the given distance from the start point in the direction of the end point.
+ /// The distance must be less than the distance between these two points.
+ ///
+ /// Starting Geometry Point
+ /// End Geometry Point
+ /// Distance measure of the point to locate
+ ///
+ public static SqlGeometry InterpolateBetweenGeom(SqlGeometry start, SqlGeometry end, double distance)
+ {
+ // We need to check a few prerequisites.
+ // We only operate on points.
+ if (!start.IsPoint() || !end.IsPoint())
+ {
+ throw new ArgumentException(ErrorMessage.PointCompatible);
+ }
+
+ // The SRIDs also have to match
+ var srid = start.STSrid.Value;
+ if (srid != end.STSrid.Value)
+ {
+ throw new ArgumentException(ErrorMessage.SRIDCompatible);
+ }
+
+ // Finally, the distance has to fall between these points.
+ var length = start.STDistance(end).Value;
+ if (distance > start.STDistance(end))
+ {
+ throw new ArgumentException(ErrorMessage.DistanceMustBeBetweenTwoPoints);
+ }
+
+ if (distance < 0)
+ {
+ throw new ArgumentException(ErrorMessage.DistanceMustBePositive);
+ }
+
+ // Since we're working on a Cartesian plane, this is now pretty simple.
+ // The fraction of the way from start to end.
+ var fraction = distance / length;
+ var newX = (start.STX.Value * (1 - fraction)) + (end.STX.Value * fraction);
+ var newY = (start.STY.Value * (1 - fraction)) + (end.STY.Value * fraction);
+ return SqlGeometry.Point(newX, newY, srid);
+ }
+
+ // Make our LocateAlongGeometrySink into a function call. This function just hooks up
+ // and runs a pipeline using the sink.
+ public static SqlGeometry LocatePointAlongGeom(SqlGeometry geometry, double distance)
+ {
+ var geometryBuilder = new SqlGeometryBuilder();
+ var geometrySink = new LocateAlongGeometrySink(distance, geometryBuilder);
+ geometry.Populate(geometrySink);
+ return geometryBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Make the input geometry valid to use in geography manipulation
+ ///
+ /// Input geometry
+ ///
+ public static SqlGeometry MakeValidForGeography(SqlGeometry geometry)
+ {
+ // Note: This function relies on an undocumented feature of the planar Union and MakeValid
+ // that polygon rings in their result will always be oriented using the same rule that
+ // is used in geography. But, it is not good practice to rely on such fact in production code.
+
+ if (geometry.STIsValid().Value && !geometry.STIsEmpty().Value)
+ return geometry.STUnion(geometry.STPointN(1));
+
+ return geometry.MakeValid();
+ }
+
+ ///
+ /// Reverse the Line Segment.
+ ///
+ /// Input SqlGeometry
+ ///
+ public static SqlGeometry ReverseLinestring(SqlGeometry geometry)
+ {
+ if (!geometry.IsLineString())
+ throw new ArgumentException(ErrorMessage.LineStringCompatible);
+
+ var geomBuilder = new SqlGeometryBuilder();
+
+ geomBuilder.SetSrid((int)geometry.STSrid);
+ geomBuilder.BeginGeometry(OpenGisGeometryType.LineString);
+ geomBuilder.BeginFigure(geometry.STEndPoint().STX.Value, geometry.STEndPoint().STY.Value);
+ for (var i = (int)geometry.STNumPoints() - 1; i >= 1; i--)
+ {
+ geomBuilder.AddLine(
+ geometry.STPointN(i).STX.Value,
+ geometry.STPointN(i).STY.Value);
+ }
+ geomBuilder.EndFigure();
+ geomBuilder.EndGeometry();
+ return geomBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Shift the input Geometry x and y co-ordinate by specified amount
+ /// Make our ShiftGeometrySink into a function call by hooking it into a simple pipeline.
+ ///
+ /// Input Geometry
+ /// X value to shift
+ /// Y value to shift
+ /// Shifted Geometry
+ public static SqlGeometry ShiftGeometry(SqlGeometry geometry, double xShift, double yShift)
+ {
+ // create a sink that will create a geometry instance
+ var geometryBuilder = new SqlGeometryBuilder();
+
+ // create a sink to do the shift and plug it in to the builder
+ var geomSink = new ShiftGeometrySink(xShift, yShift, geometryBuilder);
+
+ // plug our sink into the geometry instance and run the pipeline
+ geometry.Populate(geomSink);
+
+ // the end of our pipeline is now populated with the shifted geometry instance
+ return geometryBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// This implements a completely trivial conversion from geometry to geography, simply taking each
+ /// point (x,y) --> (long, lat). The result is assigned the given SRID.
+ ///
+ /// Input Geometry to convert
+ /// Target SRID
+ /// Converted Geography
+ public static SqlGeography VacuousGeometryToGeography(SqlGeometry toConvert, int targetSrid)
+ {
+ var geographyBuilder = new SqlGeographyBuilder();
+ toConvert.Populate(new VacuousGeometryToGeographySink(targetSrid, geographyBuilder));
+ return geographyBuilder.ConstructedGeography;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Functions/LRS/Geometry.cs b/src/Functions/LRS/Geometry.cs
new file mode 100644
index 0000000..ed5e7a2
--- /dev/null
+++ b/src/Functions/LRS/Geometry.cs
@@ -0,0 +1,1120 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Data.SqlTypes;
+using System.Linq;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Sinks.Geometry;
+using SQLSpatialTools.Types;
+using SQLSpatialTools.Utility;
+using Ext = SQLSpatialTools.Utility.SpatialExtensions;
+
+namespace SQLSpatialTools.Functions.LRS
+{
+ ///
+ /// This provides LRS data manipulation on planar Geometry data type.
+ ///
+ public static class Geometry
+ {
+ ///
+ /// Clip a geometry segment based on specified measure.
+ ///
If the clipped start and end point is within tolerance of shape point then shape point is returned as start and end of clipped Geom segment.
+ ///
This function just hooks up and runs a pipeline using the sink.
+ ///
+ /// Input Geometry
+ /// Start Measure
+ /// End Measure
+ /// Tolerance Value
+ /// Clipped Segment
+ public static SqlGeometry ClipGeometrySegment(SqlGeometry geometry, double clipStartMeasure, double clipEndMeasure, double tolerance = Constants.Tolerance)
+ {
+ return ClipAndRetainMeasure(geometry, clipStartMeasure, clipEndMeasure, tolerance, false);
+ }
+
+ ///
+ /// Clip a geometry segment and retains its measure.
+ ///
+ /// The geometry.
+ /// The clip start measure.
+ /// The clip end measure.
+ /// The tolerance.
+ /// if set to true [retain measure].
+ ///
+ private static SqlGeometry ClipAndRetainMeasure(SqlGeometry geometry, double clipStartMeasure, double clipEndMeasure, double tolerance, bool retainMeasure)
+ {
+ Ext.ThrowIfNotLRSType(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+
+ // if not multiline just return the line segment or point post clipping
+ if (!geometry.IsMultiLineString())
+ return ClipLineSegment(geometry, clipStartMeasure, clipEndMeasure, tolerance, retainMeasure);
+
+ // for multi line
+ var multiLine = geometry.GetLRSMultiLine();
+
+ var clippedSegments = new List();
+ foreach (var line in multiLine)
+ {
+ var segment = ClipLineSegment(line.ToSqlGeometry(), clipStartMeasure, clipEndMeasure, tolerance, retainMeasure);
+ // add only line segments
+ if (segment.IsNotNullOrEmpty())
+ clippedSegments.Add(segment);
+ }
+
+ if (!clippedSegments.Any()) return SqlGeometry.Null;
+ {
+ // if one segment then it is a POINT or LINESTRING, so return straight away.
+ if (clippedSegments.Count == 1)
+ return clippedSegments.First();
+
+ var geomBuilder = new SqlGeometryBuilder();
+ // count only LINESTRING
+ var multiLineGeomSink = new BuildMultiLineFromLinesSink(geomBuilder, clippedSegments.Count(segment => segment.IsLineString()));
+
+ foreach (var geom in clippedSegments)
+ {
+ // ignore points
+ if (geom.IsLineString())
+ geom.Populate(multiLineGeomSink);
+ }
+
+ return geomBuilder.ConstructedGeometry;
+ }
+
+ }
+
+ ///
+ /// Clip a geometry segment based on specified measure.
+ ///
If the clipped start and end point is within tolerance of shape point then shape point is returned as start and end of clipped Geom segment.
+ ///
This function just hooks up and runs a pipeline using the sink.
+ ///
+ /// Input Geometry
+ /// Start Measure
+ /// End Measure
+ /// Tolerance Value
+ /// Flag to retain clip measures
+ /// Clipped Segment
+ private static SqlGeometry ClipLineSegment(SqlGeometry geometry, double clipStartMeasure, double clipEndMeasure, double tolerance, bool retainClipMeasure)
+ {
+ var startMeasureInvalid = false;
+ var endMeasureInvalid = false;
+
+ // reassign clip start and end measure based upon there difference
+ if (clipStartMeasure > clipEndMeasure)
+ {
+ var shiftObj = clipStartMeasure;
+ clipStartMeasure = clipEndMeasure;
+ clipEndMeasure = shiftObj;
+ }
+
+ // if point then compute here and return
+ if (geometry.IsPoint())
+ {
+ var pointMeasure = geometry.HasM ? geometry.M.Value : 0;
+ var isClipMeasureEqual = clipStartMeasure.EqualsTo(clipEndMeasure);
+ // no tolerance check, if both start and end measure is point measure then return point
+ if (isClipMeasureEqual && pointMeasure.EqualsTo(clipStartMeasure))
+ return geometry;
+
+ if (isClipMeasureEqual && (clipStartMeasure > pointMeasure || clipStartMeasure < pointMeasure))
+ Ext.ThrowLRSError(LRSErrorCodes.InvalidLRSMeasure);
+ // if clip measure fall behind or beyond point measure then return null
+ else if ((clipStartMeasure < pointMeasure && clipEndMeasure < pointMeasure) || (clipStartMeasure > pointMeasure && clipEndMeasure > pointMeasure))
+ return null;
+ // else throw invalid LRS error.
+ else
+ Ext.ThrowLRSError(LRSErrorCodes.InvalidLRS);
+ }
+
+ // Get the measure progress of linear geometry and reassign the start and end measures based upon the progression
+ var measureProgress = geometry.STLinearMeasureProgress();
+ var geomStartMeasure = measureProgress == LinearMeasureProgress.Increasing ? geometry.GetStartPointMeasure() : geometry.GetEndPointMeasure();
+ var geomEndMeasure = measureProgress == LinearMeasureProgress.Increasing ? geometry.GetEndPointMeasure() : geometry.GetStartPointMeasure();
+
+ // if clip start measure matches geom start measure and
+ // clip end measure matches geom end measure then return the input geom
+ if (clipStartMeasure.EqualsTo(geomStartMeasure) && clipEndMeasure.EqualsTo(geomEndMeasure))
+ return geometry;
+
+ // Check if clip start and end measures are beyond geom start and end point measures
+ var isStartBeyond = clipStartMeasure < geomStartMeasure;
+ var isEndBeyond = clipEndMeasure > geomEndMeasure;
+
+ // When clip measure range is not beyond range; then don't consider tolerance on extreme math; as per Oracle
+ var isExtremeMeasuresMatch = Ext.IsExtremeMeasuresMatch(geomStartMeasure, geomEndMeasure, clipStartMeasure, clipEndMeasure);
+
+ // don't throw error when measure is not in the range
+ // rather reassign segment start and end measure
+ // if they are beyond the range or matching with the start and end point measure of input geometry
+ if (!clipStartMeasure.IsWithinRange(geometry))
+ {
+ if (isStartBeyond || isExtremeMeasuresMatch)
+ {
+ if (clipStartMeasure <= geomStartMeasure)
+ clipStartMeasure = geomStartMeasure;
+ }
+ else
+ startMeasureInvalid = true;
+ }
+
+ // end point check
+ if (!clipEndMeasure.IsWithinRange(geometry))
+ {
+ if (isEndBeyond || isExtremeMeasuresMatch)
+ {
+ if (clipEndMeasure >= geomEndMeasure)
+ clipEndMeasure = geomEndMeasure;
+ }
+ else
+ endMeasureInvalid = true;
+ }
+
+ // if both clip start and end measure are reassigned to invalid then return null
+ if (startMeasureInvalid || endMeasureInvalid)
+ return null;
+
+ // Post adjusting if clip start measure matches geom start measure and
+ // clip end measure matches geom end measure then return the input geom
+ if (clipStartMeasure.EqualsTo(geomStartMeasure) && clipEndMeasure.EqualsTo(geomEndMeasure))
+ return geometry;
+
+ // if clip start and end measure are equal post adjusting then we will return a shape point
+ if (clipStartMeasure.EqualsTo(clipEndMeasure) && (isStartBeyond || isEndBeyond))
+ {
+ if (isStartBeyond)
+ return measureProgress == LinearMeasureProgress.Increasing ? geometry.STStartPoint() : geometry.STEndPoint();
+ return measureProgress == LinearMeasureProgress.Increasing ? geometry.STEndPoint() : geometry.STStartPoint();
+ }
+
+ // if both clip start and end measure is same then don't check for distance tolerance
+ if (clipStartMeasure.NotEqualsTo(clipEndMeasure))
+ {
+ var clipStartPoint = LocatePointWithTolerance(geometry, clipStartMeasure, out bool isClipStartShapePoint, tolerance);
+ var clipEndPoint = LocatePointWithTolerance(geometry, clipEndMeasure, out bool isClipEndShapePoint, tolerance);
+ if (clipStartPoint.IsWithinTolerance(clipEndPoint, tolerance))
+ {
+ // if any one of them is a shape point return null
+ if (isClipStartShapePoint || isClipEndShapePoint)
+ return null;
+ else
+ {
+ var lrsLine = new LRSLine((int)clipStartPoint.STSrid);
+ // based on measure progress re-arrange the points.
+ if (measureProgress == LinearMeasureProgress.Increasing)
+ {
+ lrsLine.AddPoint(clipStartPoint);
+ lrsLine.AddPoint(clipEndPoint);
+ }
+ else
+ {
+ lrsLine.AddPoint(clipEndPoint);
+ lrsLine.AddPoint(clipStartPoint);
+ }
+ return lrsLine.ToSqlGeometry();
+ }
+ }
+ }
+
+ var geometryBuilder = new SqlGeometryBuilder();
+ var geomSink = new ClipMGeometrySegmentSink(clipStartMeasure, clipEndMeasure, geometryBuilder, tolerance, retainClipMeasure);
+ geometry.Populate(geomSink);
+ return geometryBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// calculate measure value across shape points.
+ ///
+ /// Input Geometry
+ ///
+ ///
+ ///
+ public static SqlGeometry ConvertToLrsGeom(SqlGeometry geometry, double? startMeasure, double? endMeasure)
+ { //Line string geometry should not contain measure information.
+ Ext.ThrowIfNotLRSType(geometry);
+ if (geometry.HasZ || geometry.HasM)
+ throw new ArgumentException(ErrorMessage.LineOrMultiLineStringCompatible);
+
+ var NullConditonFirst = (startMeasure == null || endMeasure == null);
+ var NullConditionSecond = (startMeasure == null && endMeasure == null);
+
+ //if start measure or end measure is null then it returns null geometry
+ if (NullConditonFirst &&!NullConditionSecond)
+ {
+ return null;
+ }
+ //point always takes start measure value if start and end measure values are not null
+ if (geometry.IsPoint())
+ {
+ var pointMeasure = NullConditionSecond ? 0 : (double)startMeasure;
+ return Ext.GetPointWithUpdatedM(geometry, pointMeasure);
+ }
+
+ // segment length
+ var segmentLength = geometry.STLength().Value;
+
+ // As per requirement;
+ // the default value of start point is 0 when null is specified
+ // the default value of end point is cartographic length of the segment when null is specified
+ var localStartMeasure = startMeasure ?? 0;
+ var localEndMeasure = endMeasure ?? segmentLength;
+ //internally ConvertToLrsGeom uses PopulateGeometry functionalities
+ var geomSink = new PopulateGeometryMeasuresSink(localStartMeasure, localEndMeasure, segmentLength);
+ geometry.Populate(geomSink);
+ return geomSink.GetConstructedGeom();
+ }
+
+ ///
+ /// Get end point measure of a LRS Geom Segment.
+ ///
+ /// Input Geometry
+ /// End measure
+ public static SqlDouble GetEndMeasure(SqlGeometry geometry)
+ {
+ Ext.ThrowIfNotLRSType(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+ return geometry.GetEndPointMeasure();
+ }
+
+ ///
+ /// Get start point measure of a LRS Geom Segment.
+ ///
+ /// Input Geometry
+ /// Start measure
+ public static SqlDouble GetStartMeasure(SqlGeometry geometry)
+ {
+ Ext.ThrowIfNotLRSType(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+ return geometry.GetStartPointMeasure();
+ }
+
+ ///
+ /// Find the point with specified measure, going from the start point in the direction of the end point.
+ /// The measure must be between measures of these two points.
+ ///
+ /// Start Geometry Point
+ /// End Geometry Point
+ /// Measure at which the point is to be found
+ ///
+ public static SqlGeometry InterpolateBetweenGeom(SqlGeometry startPoint, SqlGeometry endPoint, double measure)
+ {
+ // We need to check a few prerequisite.
+ // We only operate on points.
+ Ext.ThrowIfNotPoint(startPoint, endPoint);
+ Ext.ThrowIfSRIDDoesNotMatch(startPoint, endPoint);
+ Ext.ValidateLRSDimensions(ref startPoint);
+ Ext.ValidateLRSDimensions(ref endPoint);
+ Ext.ThrowIfMeasureIsNotInRange(measure, startPoint, endPoint);
+
+ // The SRIDs also have to match
+ var srid = startPoint.STSrid.Value;
+
+ // Since we're working on a Cartesian plane, this is now pretty simple.
+ // The fraction of the way from start to end.
+ var fraction = (measure - startPoint.M.Value) / (endPoint.M.Value - startPoint.M.Value);
+ var newX = (startPoint.STX.Value * (1 - fraction)) + (endPoint.STX.Value * fraction);
+ var newY = (startPoint.STY.Value * (1 - fraction)) + (endPoint.STY.Value * fraction);
+
+ //There's no way to know Z, so just put NULL there
+ return Ext.GetPoint(newX, newY, null, measure, srid);
+ }
+
+ ///
+ /// Method returns the Merge position of two LRS segments bound by the tolerance, if the segments are connected
+ /// Otherwise return False in string format
+ ///
+ /// Geometry Segment 1
+ /// Geometry Segment 2
+ /// tolerance
+ /// Merge position if connected else false
+ public static string GetMergePosition(SqlGeometry geometry1, SqlGeometry geometry2, double tolerance = Constants.Tolerance)
+ {
+ // if the segments are connected, return the merge position
+ var isConnected = CheckIfConnected(geometry1, geometry2, tolerance, out var mergePosition);
+ // for not connected segments, return false in string type
+ return isConnected ? mergePosition.ToString() : "false";
+ }
+
+ ///
+ /// Checks if two geometric segments are spatially connected.
+ ///
+ ///
+ ///
+ ///
+ /// SqlBoolean
+ public static SqlBoolean IsConnected(SqlGeometry geometry1, SqlGeometry geometry2, double tolerance = Constants.Tolerance)
+ {
+ return CheckIfConnected(geometry1, geometry2, tolerance, out _);
+ }
+
+ ///
+ /// Checks if two geometric segments are spatially connected with merge position information
+ ///
+ /// First Geometry
+ /// Second Geometry
+ /// Distance Threshold range; default 0.01F
+ ///
+ /// SqlBoolean
+ private static SqlBoolean CheckIfConnected(SqlGeometry geometry1, SqlGeometry geometry2, double tolerance, out MergePosition mergePosition)
+ {
+ Ext.ThrowIfNotLRSType(geometry1, geometry2);
+ Ext.ThrowIfSRIDDoesNotMatch(geometry1, geometry2);
+ Ext.ValidateLRSDimensions(ref geometry1);
+ Ext.ValidateLRSDimensions(ref geometry2);
+
+ // check for point type
+ if (geometry1.IsPoint() && geometry2.IsPoint())
+ {
+ var result = geometry1.IsXYWithinRange(geometry2, tolerance);
+ mergePosition = result ? MergePosition.BothEnds : MergePosition.None;
+ return result;
+ }
+
+ // Geometry 1 points
+ var geometry1StartPoint = geometry1.STStartPoint();
+ var geometry1EndPoint = geometry1.STEndPoint();
+
+ // Geometry 2 points
+ var geometry2StartPoint = geometry2.STStartPoint();
+ var geometry2EndPoint = geometry2.STEndPoint();
+
+ // If the points doesn't coincide, check for the point co-ordinate difference and whether it falls within the tolerance
+ // distance not considered as per Oracle.
+ // Comparing geom1 start point x and y co-ordinate difference against geom2 start and end point x and y co-ordinates
+ var isStartStartConnected = geometry1StartPoint.STEquals(geometry2StartPoint) || geometry1StartPoint.IsXYWithinRange(geometry2StartPoint, tolerance);
+ var isStartEndConnected = geometry1StartPoint.STEquals(geometry2EndPoint) || geometry1StartPoint.IsXYWithinRange(geometry2EndPoint, tolerance);
+ var isEndStartConnected = geometry1EndPoint.STEquals(geometry2StartPoint) || geometry1EndPoint.IsXYWithinRange(geometry2StartPoint, tolerance);
+ var isEndEndConnected = geometry1EndPoint.STEquals(geometry2EndPoint) || geometry1EndPoint.IsXYWithinRange(geometry2EndPoint, tolerance);
+ var isBothEndsConnected = isStartStartConnected && isEndEndConnected;
+ var isCrossEndsConnected = isStartEndConnected && isEndStartConnected;
+
+ mergePosition = MergePosition.None;
+
+ if (isStartStartConnected)
+ mergePosition = MergePosition.StartStart;
+ if (isStartEndConnected)
+ mergePosition = MergePosition.StartEnd;
+
+ if (isEndStartConnected)
+ mergePosition = MergePosition.EndStart;
+ if (isEndEndConnected)
+ mergePosition = MergePosition.EndEnd;
+
+ if (isBothEndsConnected)
+ mergePosition = MergePosition.BothEnds;
+ if (isCrossEndsConnected)
+ mergePosition = MergePosition.CrossEnds;
+
+ if (isStartStartConnected || isStartEndConnected || isEndStartConnected || isEndEndConnected)
+ return true;
+ return false;
+ }
+
+ ///
+ /// Checks if an LRS point is valid.
+ ///
+ /// Sql Geometry.
+ ///
+ public static SqlBoolean IsValidPoint(SqlGeometry geometry)
+ {
+ if (geometry.IsNullOrEmpty() || !geometry.STIsValid() || !geometry.IsPoint())
+ return false;
+
+ // check if the point has measure value
+ if (!geometry.M.IsNull)
+ return true;
+
+ // if m is null; the check if frame from x,y,z where z is m
+ if (geometry.STGetDimension() != DimensionalInfo.Dim3D) return false;
+ geometry = geometry.ConvertTo2DimensionWithMeasure();
+ return !geometry.M.IsNull;
+ }
+
+ ///
+ /// Locate the Geometry Point along the specified measure on the Geometry.
+ /// This function just hooks up and runs a pipeline using the sink.
+ ///
+ /// Input Geometry
+ /// Measure of the Geometry point to locate
+ /// Geometry Point
+ public static SqlGeometry LocatePointAlongGeom(SqlGeometry geometry, double measure)
+ {
+ // Invoking locate point without tolerance
+ return LocatePointWithTolerance(geometry, measure, out _, 0);
+ }
+
+ ///
+ /// Locates the point with tolerance.
+ ///
+ /// The geometry.
+ /// The measure.
+ ///
+ /// The tolerance.
+ ///
+ private static SqlGeometry LocatePointWithTolerance(SqlGeometry geometry, double measure, out bool isShapePoint, double tolerance = Constants.Tolerance)
+ {
+ Ext.ThrowIfNotLRSType(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+ Ext.ThrowIfMeasureIsNotInRange(measure, geometry);
+
+ // If input geom is point; its a no-op just return the same.
+ if (geometry.IsPoint())
+ {
+ isShapePoint = true;
+ return geometry;
+ }
+
+ var geomBuilder = new SqlGeometryBuilder();
+ var geomSink = new LocateMAlongGeometrySink(measure, geomBuilder, tolerance);
+ geometry.Populate(geomSink);
+
+ // if point is not derived then the measure is not in range.
+ if (!geomSink.IsPointDerived)
+ Ext.ThrowLRSError(LRSErrorCodes.InvalidLRSMeasure);
+
+ isShapePoint = geomSink.IsShapePoint;
+ return geomBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Merge two geometry segments to one geometry.
+ /// This function just hooks up and runs a pipeline using the sink.
+ ///
+ /// First Geometry
+ /// Second Geometry
+ /// Tolerance
+ /// Returns Merged Geometry Segments
+ public static SqlGeometry MergeGeometrySegments(SqlGeometry geometry1, SqlGeometry geometry2, double tolerance = Constants.Tolerance)
+ {
+ Ext.ThrowIfNotLRSType(geometry1, geometry2);
+ Ext.ThrowIfSRIDDoesNotMatch(geometry1, geometry2);
+ Ext.ValidateLRSDimensions(ref geometry1);
+ Ext.ValidateLRSDimensions(ref geometry2);
+
+ // return object
+ SqlGeometry returnGeom = null;
+
+ // returning geometry2 if both the geometries are points
+ if (geometry1.CheckGeomPoint() && geometry2.CheckGeomPoint())
+ return geometry2;
+
+ // If either of the input geom is point; then return the other geometry.
+ if (geometry1.CheckGeomPoint())
+ return geometry2;
+
+ if (geometry2.CheckGeomPoint())
+ return geometry1;
+
+ var isConnected = CheckIfConnected(geometry1, geometry2, tolerance, out var mergePosition);
+ var mergeType = geometry1.GetMergeType(geometry2);
+
+ if (isConnected)
+ {
+ switch (mergeType)
+ {
+ case MergeInputType.LSLS:
+ returnGeom = MergeConnectedLineStrings(geometry1, geometry2, mergePosition, out _);
+ break;
+ case MergeInputType.LSMLS:
+ case MergeInputType.MLSLS:
+ case MergeInputType.MLSMLS:
+ returnGeom = MergeConnectedMultiLineStrings(geometry1, geometry2, mergePosition);
+ break;
+ }
+ }
+ else
+ {
+ // construct multi line
+ returnGeom = MergeDisconnectedLineSegments(geometry1, geometry2);
+ }
+ return returnGeom;
+ }
+
+ ///
+ /// Merge the segments bound with tolerance and resets the measure from zero
+ ///
+ ///
+ ///
+ ///
+ /// SqlGeometry
+ public static SqlGeometry MergeAndResetGeometrySegments(SqlGeometry geometry1, SqlGeometry geometry2, double tolerance = Constants.Tolerance)
+ {
+ var resultantGeometry = MergeGeometrySegments(geometry1, geometry2, tolerance);
+ return PopulateGeometryMeasures(resultantGeometry, null, null);
+ }
+
+ ///
+ /// Method will merge simple line strings with tolerance and returns the merged line segment by considering measure and direction of the first geometry.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// SqlGeometry
+ private static SqlGeometry MergeConnectedLineStrings(SqlGeometry geometry1, SqlGeometry geometry2, MergePosition mergePosition, out double measureDifference)
+ {
+ // geometry 1 and geometry 2 to be 2D line strings with measure 'm'
+ Ext.ThrowIfNotLine(geometry1, geometry2);
+ // offset measure difference.
+ double offsetM;
+
+ // references governs the order of geometries to get merge
+ SqlGeometry targetSegment, sourceSegment;
+
+ // check direction of measure.
+ var isSameDirection = geometry1.STSameDirection(geometry2);
+
+ // segments must be connected in any of the following position.
+ switch (mergePosition)
+ {
+ case MergePosition.EndStart:
+ case MergePosition.CrossEnds:
+ {
+ // Single negation of measure is needed for geometry 2,
+ // if both segments are differ in measure variation
+ if (!isSameDirection)
+ geometry2 = MultiplyGeometryMeasures(geometry2, -1);
+
+ offsetM = geometry1.STEndPoint().GetPointOffset(geometry2.STStartPoint());
+ geometry2 = TranslateMeasure(geometry2, offsetM);
+ sourceSegment = geometry1;
+ targetSegment = geometry2;
+ break;
+ }
+ case MergePosition.EndEnd:
+ case MergePosition.BothEnds:
+ {
+ // Double negation is needed for geometry 2, i.e., both segments differ from measure variation,
+ // also, geometry 2 has been traversed from ending point to the starting point
+ if (isSameDirection)
+ geometry2 = MultiplyGeometryMeasures(geometry2, -1);
+
+ offsetM = geometry1.STEndPoint().GetPointOffset(geometry2.STEndPoint());
+ // Reverse the geometry 2, since it has been traversed from ending point to the starting point
+ // scale the measures of geometry 2 based on the offset measure difference between them
+ geometry2 = ReverseAndTranslateGeometry(geometry2, offsetM);
+ // start traversing from the geometry 1, hence g1 would be the source geometry
+ sourceSegment = geometry1;
+ targetSegment = geometry2;
+ break;
+ }
+ case MergePosition.StartStart:
+ {
+ // Double negation is needed for geometry 2, i.e., both segments differ from measure variation,
+ // also, geometry 2 has been traversed from ending point to the starting point
+ if (isSameDirection)
+ geometry2 = MultiplyGeometryMeasures(geometry2, -1);
+
+ offsetM = geometry1.STStartPoint().GetPointOffset(geometry2.STStartPoint());
+ // Reverse the geometry 2, since it has been traversed from ending point to the starting point
+ // scale the measures of geometry 2 based on the offset measure difference between them
+ geometry2 = ReverseAndTranslateGeometry(geometry2, offsetM);
+ // the starting point of g1 will become the intermediate point of resultant, so source geometry would be geometry 2
+ sourceSegment = geometry2;
+ targetSegment = geometry1;
+ break;
+ }
+ case MergePosition.StartEnd:
+ {
+ // Single negation of measure is needed for geometry 2
+ // if both segments are differ in measure variation
+ if (!isSameDirection)
+ geometry2 = MultiplyGeometryMeasures(geometry2, -1);
+
+ offsetM = (geometry1.STStartPoint().M.Value - geometry2.STEndPoint().M.Value);
+ // scale the measures of geometry 2 based on the offset measure difference between them
+ geometry2 = TranslateMeasure(geometry2, offsetM);
+ // the starting point of g1 will become the intermediate point of resultant, so source geometry would be geometry 2
+ sourceSegment = geometry2;
+ targetSegment = geometry1;
+ break;
+ }
+ default:
+ throw new Exception("Invalid Merge Coordinate position");
+ }
+ measureDifference = offsetM; // gives the offset measure difference for the caller method consumption
+ // Builder for resultant merged geometry to store
+ var geomBuilder = new SqlGeometryBuilder();
+
+ // Building a line segment from the range of points by excluding the last point ( Merging point )
+ var segment1 = new LineStringMergeGeometrySink(geomBuilder, true, sourceSegment.STNumPoints());
+ sourceSegment.Populate(segment1);
+
+ // Continuing to build segment from the points of second geometry
+ var segment2 = new LineStringMergeGeometrySink(geomBuilder, false, targetSegment.STNumPoints());
+ targetSegment.Populate(segment2);
+
+ return geomBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Merges the Multi line and line string combinations with connected structure
+ ///
+ ///
+ ///
+ ///
+ /// SqlGeometry of type MultiLineString
+ private static SqlGeometry MergeConnectedMultiLineStrings(SqlGeometry geometry1, SqlGeometry geometry2, MergePosition mergePosition)
+ {
+ // check direction of measure.
+ var isSameDirection = geometry1.STSameDirection(geometry2);
+
+ SqlGeometry sourceSegment, targetSegment, mergedSegment;
+
+ var segment1 = geometry1.GetLRSMultiLine();
+ var segment2 = geometry2.GetLRSMultiLine();
+
+ switch (mergePosition)
+ {
+ case MergePosition.EndEnd:
+ case MergePosition.BothEnds:
+ {
+ // Double Negation of measure is needed, since geometry2 has been traversed from end point to the starting point also differ from measure variation
+ if (isSameDirection)
+ segment2.ScaleMeasure(-1);
+
+ sourceSegment = segment1.GetLastLine().ToSqlGeometry();
+ targetSegment = segment2.GetLastLine().ToSqlGeometry();
+ // Generating merged segment of geometry1 and geometry2
+ mergedSegment = MergeConnectedLineStrings(sourceSegment, targetSegment, mergePosition, out var measureDifference);
+
+ var mergedGeom = mergedSegment.GetLRSMultiLine();
+
+ segment1.RemoveLast(); // Removing merging line segment from the geometry1
+ segment2.RemoveLast(); // Removing merging line segment from the geometry2
+
+ segment2.ReverseLinesAndPoints(); // Traversing from end to the start of geometry2. So reversing the
+ segment2.TranslateMeasure(measureDifference); // Translating the offset measure difference in segment2
+
+ segment1.Add(mergedGeom); // appending merged segment line to the segment1 , since geometry1 would be the beginning geometry of the resultant geometry
+ segment1.Add(segment2); // appending remaining segment of updated geometry2 with the segment1
+ return segment1.ToSqlGeometry(); // converting to SqlGeometry type
+ }
+ case MergePosition.EndStart:
+ case MergePosition.CrossEnds:
+ {
+ // Negation of measure is needed, since measure variation of geometry2 differs from that of geometry1
+ if (!isSameDirection)
+ segment2.ScaleMeasure(-1);
+
+ sourceSegment = segment1.GetLastLine().ToSqlGeometry();
+ targetSegment = segment2.GetFirstLine().ToSqlGeometry();
+ // Generating merged segment of geometry1 and geometry2
+ mergedSegment = MergeConnectedLineStrings(sourceSegment, targetSegment, mergePosition, out var measureDifference);
+
+ var mergedGeom = mergedSegment.GetLRSMultiLine();
+
+ segment1.RemoveLast(); // Removing merging line segment from the geometry1
+ segment2.RemoveFirst(); // Removing merging line segment from the geometry2
+
+ segment2.TranslateMeasure(measureDifference); // Translating the offset measure difference in segment2
+
+ segment1.Add(mergedGeom); // Appending merged segment line to the segment1 , since geometry1 would be the beginning geometry of the resultant geometry
+ segment1.Add(segment2); // Appending remaining segment of updated geometry2 with the segment1
+ return segment1.ToSqlGeometry(); // converting to SqlGeometry type
+ }
+ case MergePosition.StartEnd:
+ {
+ // Negation of measure is needed, since measure variation of geometry2 differs from that of geometry1
+ if (!isSameDirection)
+ segment2.ScaleMeasure(-1);
+
+ sourceSegment = segment1.GetFirstLine().ToSqlGeometry();
+ targetSegment = segment2.GetLastLine().ToSqlGeometry();
+ // Generating merged segment of geometry1 and geometry2
+ mergedSegment = MergeConnectedLineStrings(sourceSegment, targetSegment, mergePosition, out var measureDifference);
+
+ var mergedGeom = mergedSegment.GetLRSMultiLine();
+
+ segment1.RemoveFirst(); // Removing merging line segment from the geometry1
+ segment2.RemoveLast(); // Removing merging line segment from the geometry2
+
+ segment2.TranslateMeasure(measureDifference); // Translating the offset measure difference in segment2
+
+ segment2.Add(mergedGeom); // Appending merged segment line to the segment2 ,since geometry1 would be the beginning geometry of the resultant geometry
+ segment2.Add(segment1); // Appending remaining segments of geometry1 with the geometry2
+ return segment2.ToSqlGeometry(); // converting to SqlGeometry type
+ }
+ case MergePosition.StartStart:
+ {
+ // Double Negation of measure is needed, since geometry2 has been traversed from end point to the starting point also differ from measure variation
+ if (isSameDirection)
+ segment2.ScaleMeasure(-1);
+
+ sourceSegment = segment1.GetFirstLine().ToSqlGeometry();
+ targetSegment = segment2.GetFirstLine().ToSqlGeometry();
+ // Generating merged segment of geometry1 and geometry2
+ mergedSegment = MergeConnectedLineStrings(sourceSegment, targetSegment, mergePosition, out var measureDifference);
+
+ var mergedGeom = mergedSegment.GetLRSMultiLine();
+
+ segment1.RemoveFirst(); // Removing merging line segment from the geometry1
+ segment2.RemoveFirst(); // Removing merging line segment from the geometry2
+
+ segment2.ReverseLinesAndPoints(); // Reversing the lines and its corresponding points of segment2, Since it has been traversed from end to start
+ segment2.TranslateMeasure(measureDifference); // Translating the offset measure difference in segment2
+
+ segment2.Add(mergedGeom); // Appending merged segment line to the segment2 ,since geometry1 would be the beginning geometry of the resultant geometry
+ segment2.Add(segment1); // Appending remaining segments of geometry1 with the geometry2
+ return segment2.ToSqlGeometry(); // converting to SqlGeometry type
+ }
+ default:
+ return null;
+ }
+ }
+
+ ///
+ /// Build MULTILINESTRING from two input geometry segments [LINESTRING, MULTILINESTRING]
+ /// This should be called for merging geom segments when they are not connected.
+ /// Here the offset measure is updated with the measure of second segment.
+ ///
+ /// The geometry1.
+ /// The geometry2.
+ ///
+ private static SqlGeometry MergeDisconnectedLineSegments(SqlGeometry geometry1, SqlGeometry geometry2)
+ {
+ var isSameDirection = geometry1.STSameDirection(geometry2);
+ var firstSegmentDirection = geometry1.STLinearMeasureProgress();
+ if (!isSameDirection)
+ geometry2 = MultiplyGeometryMeasures(geometry2, -1);
+
+ var offsetM = geometry1.GetOffset(geometry2);
+ var doUpdateM = false;
+
+ if (isSameDirection)
+ {
+ if (firstSegmentDirection == LinearMeasureProgress.Increasing && offsetM > 0)
+ doUpdateM = true;
+
+ if (firstSegmentDirection == LinearMeasureProgress.Decreasing && offsetM < 0)
+ doUpdateM = true;
+ }
+ else
+ doUpdateM = true;
+
+ var mergedLRSMultiLine = geometry1.GetLRSMultiLine();
+ mergedLRSMultiLine.Add(geometry2.GetLRSMultiLine(doUpdateM, offsetM));
+
+ return mergedLRSMultiLine.ToSqlGeometry();
+ }
+
+ ///
+ /// Multiply the measure values of Linear Geometry
+ /// Works only for POINT, LINESTRING, MULTILINESTRING Geometry.
+ ///
+ /// Input Geometry
+ /// Measure to be Multiplied
+ ///
+ public static SqlGeometry MultiplyGeometryMeasures(SqlGeometry geometry, double multiplyMeasure)
+ {
+ Ext.ThrowIfNotLRSType(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+
+ // if scale measure is zero; return the input
+ if (multiplyMeasure.EqualsTo(0))
+ return geometry;
+
+ var geometryBuilder = new SqlGeometryBuilder();
+ var geomSink = new MultiplyMeasureGeometrySink(geometryBuilder, multiplyMeasure);
+ geometry.Populate(geomSink);
+ return geometryBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Returns the geometric segment at a specified offset from a geometric segment.
+ /// Works only for LineString and MultiLineString Geometry; Point is not supported.
+ ///
+ /// Input Geometry
+ /// Start Measure
+ /// End Measure
+ /// Offset value
+ /// Tolerance
+ /// Offset Geometry Segment
+ public static SqlGeometry OffsetGeometrySegment(SqlGeometry geometry, double startMeasure, double endMeasure, double offset, double tolerance = Constants.Tolerance)
+ {
+ // If point throw invalid LRS Segment error.
+ if (geometry.IsPoint())
+ Ext.ThrowLRSError(LRSErrorCodes.InvalidLRS);
+
+ Ext.ThrowIfNotLineOrMultiLine(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+
+ // to retain clip measures on offset
+ var clippedGeometry = ClipAndRetainMeasure(geometry, startMeasure, endMeasure, tolerance, true);
+
+ // if clipped segment is null; then return null.
+ if (clippedGeometry == null)
+ return null;
+
+ // Explicit handle if clipped segment is Point
+ // As point has a single co-ordinate we need to consider the angle from input segment, not from the clipped segment
+ var lrsSegment = clippedGeometry.IsPoint() ? geometry.GetLRSMultiLine() : clippedGeometry.GetLRSMultiLine();
+
+ if (clippedGeometry.IsPoint())
+ {
+ // Computing offset
+ var parallelSegment = lrsSegment.ComputeOffset(offset, tolerance);
+
+ // get the offset point at clipped measure
+ var offsetPoint = parallelSegment.GetPointAtM(clippedGeometry.M.Value);
+ return offsetPoint?.ToSqlGeometry();
+ }
+ else
+ {
+ // removing collinear points
+ lrsSegment.RemoveCollinearPoints();
+
+ // Computing offset
+ var parallelSegment = lrsSegment.ComputeOffset(offset, tolerance);
+
+ // if it is a two point line string; then check for distance
+ if (parallelSegment.Is2PointLine)
+ {
+ var firstPoint = parallelSegment.GetFirstLine().GetStartPoint();
+ var secondPoint = parallelSegment.GetFirstLine().GetEndPoint();
+
+ if (firstPoint.IsXYWithinTolerance(secondPoint, tolerance))
+ {
+ // always the resultant is first point from the parallel segment
+ // and measure being updated with minimum of start and end measure;
+ firstPoint.M = Math.Min(startMeasure, endMeasure);
+ return firstPoint.ToSqlGeometry();
+ }
+ }
+ parallelSegment.PopulateMeasures(parallelSegment.GetStartPointM(), parallelSegment.GetEndPointM());
+
+ return parallelSegment.ToSqlGeometry();
+ }
+ }
+
+ ///
+ /// (Re)populate measures across shape points.
+ ///
+ /// Input Geometry
+ ///
+ ///
+ ///
+ public static SqlGeometry PopulateGeometryMeasures(SqlGeometry geometry, double? startMeasure, double? endMeasure)
+ {
+ Ext.ThrowIfNotLRSType(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+
+ // check for point type
+ if (geometry.CheckGeomPoint())
+ {
+ // if anyone measure is null; then point measure is 0 else point measure is end measure
+ var pointMeasure = startMeasure == null || endMeasure == null ? 0 : (double)endMeasure;
+ return Ext.GetPointWithUpdatedM(geometry, pointMeasure);
+ }
+
+ // if anyone is null then assign null to other
+ if (startMeasure == null || endMeasure == null)
+ startMeasure = endMeasure = null;
+
+ // segment length
+ var segmentLength = geometry.STLength().Value;
+
+ // As per requirement;
+ // the default value of start point is 0 when null is specified
+ // the default value of end point is cartographic length of the segment when null is specified
+ var localStartMeasure = startMeasure ?? 0;
+ var localEndMeasure = endMeasure ?? segmentLength;
+
+ var geomSink = new PopulateGeometryMeasuresSink(localStartMeasure, localEndMeasure, segmentLength);
+ geometry.Populate(geomSink);
+ return geomSink.GetConstructedGeom();
+ }
+ ///
+ /// Resets Geometry Measure values.
+ ///
+ /// Input Geometry
+ ///
+ public static SqlGeometry ResetMeasure(SqlGeometry geometry)
+ {
+ Ext.ThrowIfNotLRSType(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+
+ var geomBuilder = new SqlGeometryBuilder();
+ var geomSink = new ResetMGeometrySink(geomBuilder);
+ geometry.Populate(geomSink);
+ return geomBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Reverse Linear Geometry
+ /// Works only for POINT, LINESTRING, MULTILINESTRING Geometry.
+ ///
+ /// Input Geometry
+ ///
+ public static SqlGeometry ReverseLinearGeometry(SqlGeometry geometry)
+ {
+ Ext.ThrowIfNotLRSType(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+
+ // if point its no-op
+ if (geometry.IsPoint())
+ return geometry;
+
+ var geometryBuilder = new SqlGeometryBuilder();
+ var geomSink = new ReverseLinearGeometrySink(geometryBuilder);
+ geometry.Populate(geomSink);
+ return geometryBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Reverse and translate Linear Geometry
+ /// Works only for POINT, LINESTRING, MULTILINESTRING Geometry.
+ ///
+ /// Input Geometry
+ ///
+ ///
+ public static SqlGeometry ReverseAndTranslateGeometry(SqlGeometry geometry, double translateMeasure)
+ {
+ Ext.ThrowIfNotLRSType(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+
+ // if point then return new point with updated measure
+ if (geometry.IsPoint())
+ return Ext.GetPointWithUpdatedM(geometry, geometry.M.Value + translateMeasure);
+
+ var geometryBuilder = new SqlGeometryBuilder();
+ var geomSink = new ReverseAndTranslateGeometrySink(geometryBuilder, translateMeasure);
+ geometry.Populate(geomSink);
+ return geometryBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Scale the measure values of the Linear Geometry
+ /// Works only for POINT, LINESTRING, MULTILINESTRING Geometry.
+ ///
+ /// Input Geometry
+ /// Start Measure
+ /// End Measure
+ /// Measure to be Multiplied
+ ///
+ public static SqlGeometry ScaleGeometrySegment(SqlGeometry geometry, double startMeasure, double endMeasure, double shiftMeasure)
+ {
+ Ext.ThrowIfNotLRSType(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+ Ext.ThrowIfMeasureNotLinear(geometry);
+
+ var geometryBuilder = new SqlGeometryBuilder();
+ var geomSink = new ScaleGeometrySink(geometryBuilder, geometry.GetStartPointMeasure(), geometry.GetEndPointMeasure(), startMeasure, endMeasure, shiftMeasure);
+ geometry.Populate(geomSink);
+ return geometryBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Split a geometry into geometry segments based on split measure.
+ /// This function just hooks up and runs a pipeline using the sink.
+ ///
+ /// Input Geometry
+ ///
+ /// First Geometry Segment
+ /// Second Geometry Segment
+ public static void SplitGeometrySegment(SqlGeometry geometry, double splitMeasure, out SqlGeometry geometry1, out SqlGeometry geometry2)
+ {
+ Ext.ThrowIfNotLRSType(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+
+ // assign default null values to out param.
+ geometry1 = null;
+ geometry2 = null;
+
+ // for point validation.
+ if (geometry.CheckGeomPoint())
+ {
+ var pointMeasure = geometry.HasM ? geometry.M.Value : 0;
+ if (pointMeasure.NotEqualsTo(splitMeasure))
+ Ext.ThrowLRSError(LRSErrorCodes.InvalidLRSMeasure);
+ return;
+ }
+
+ var startPointM = geometry.GetStartPointMeasure();
+ var endPointM = geometry.GetEndPointMeasure();
+ var ifNotLinear = !geometry.STHasLinearMeasure();
+
+ // if start point and end point is equal and it is equal to split measure then return null
+ if (geometry.STHasEqualStartAndEndMeasure() && startPointM.EqualsTo(splitMeasure))
+ return;
+
+ // if start measure is split measure then segment 1 is null and segment 2 is input geom
+ if (startPointM.EqualsTo(splitMeasure))
+ {
+ geometry2 = geometry;
+ return;
+ }
+
+ // if end measure is split measure then segment 2 is null and segment 1 is input geom
+ if (endPointM.EqualsTo(splitMeasure))
+ {
+ geometry1 = geometry;
+ return;
+ }
+
+ // if geom is multiline and the segment doesn't has linear measure;
+ // reassign the split measure to start or end measure as per progress
+ if (geometry.IsMultiLineString() && ifNotLinear)
+ {
+ var maxMeasure = startPointM > endPointM ? startPointM : endPointM;
+ if (splitMeasure > maxMeasure)
+ splitMeasure = maxMeasure;
+ }
+
+ // measure range validation is handled inside LocatePoint
+ var splitPoint = LocatePointAlongGeom(geometry, splitMeasure);
+
+ var geomSink = new SplitGeometrySegmentSink(splitPoint);
+ geometry.Populate(geomSink);
+ geometry1 = geomSink.Segment1;
+ geometry2 = geomSink.Segment2;
+ }
+
+ ///
+ /// Translates the measure values of Input Geometry
+ ///
+ /// The geometry.
+ /// The translate measure.
+ /// SqlGeometry with translated measure.
+ public static SqlGeometry TranslateMeasure(SqlGeometry geometry, double translateMeasure)
+ {
+ Ext.ThrowIfNotLRSType(geometry);
+ Ext.ValidateLRSDimensions(ref geometry);
+
+ var geomBuilder = new SqlGeometryBuilder();
+ var geomSink = new TranslateMeasureGeometrySink(geomBuilder, translateMeasure);
+ geometry.Populate(geomSink);
+ return geomBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Validates the LRS geometry.
+ ///
+ /// The input SqlGeometry.
+ /// TRUE if Valid; 13331 - if Invalid; 13333 - if Invalid Measure
+ public static string ValidateLRSGeometry(SqlGeometry geometry)
+ {
+ // throw if type apart from POINT, LINESTRING, MULTILINESTRING is given as input.
+ Ext.ThrowIfNotLRSType(geometry);
+
+ // check for dimension
+ if (geometry.STGetDimension() == DimensionalInfo.Dim2D)
+ {
+ // If there is no measure value; return invalid.
+ return LRSErrorCodes.InvalidLRS.Value();
+ }
+
+ // convert to valid 3 point LRS co-ordinate.
+ Ext.ValidateLRSDimensions(ref geometry);
+
+ // return invalid if empty or is of geometry collection
+ if (geometry.IsNullOrEmpty() || !geometry.STIsValid() || geometry.IsGeometryCollection())
+ return LRSErrorCodes.InvalidLRS.Value();
+
+ // return invalid if geometry doesn't or have null values or checks if the measures are in linear range.
+ return geometry.STHasLinearMeasure() ? LRSErrorCodes.ValidLRS.Value() : LRSErrorCodes.InvalidLRSMeasure.Value();
+ }
+ }
+}
diff --git a/src/Functions/Util/Geometry.cs b/src/Functions/Util/Geometry.cs
new file mode 100644
index 0000000..253d62c
--- /dev/null
+++ b/src/Functions/Util/Geometry.cs
@@ -0,0 +1,202 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Sinks.Geometry;
+using SQLSpatialTools.Utility;
+using Ext = SQLSpatialTools.Utility.SpatialExtensions;
+
+namespace SQLSpatialTools.Functions.Util
+{
+ ///
+ /// Utility class to manipulate planar Geometry data type.
+ ///
+ public static class Geometry
+ {
+ public static SqlGeometry ExtractGeometry(SqlGeometry sqlGeometry, int elementIndex, int ringIndex = 0)
+ {
+ if (sqlGeometry.IsNullOrEmpty())
+ return sqlGeometry;
+
+ // GEOMETRYCOLLECTION
+ if(sqlGeometry.IsGeometryCollection())
+ {
+ if (elementIndex == 0 || elementIndex > sqlGeometry.STNumGeometries())
+ Ext.ThrowInvalidElementIndex();
+
+ // reset geometry and element index and pass through
+ sqlGeometry = sqlGeometry.STGeometryN(elementIndex);
+ elementIndex = 1;
+
+ // if the resultant is MULTILINE; reset the ring index and assign it to element index
+ if(sqlGeometry.IsMultiLineString())
+ {
+ elementIndex = ringIndex;
+ ringIndex = 0;
+ }
+ }
+
+ // Handle for Curve Polygon with Compound Curve
+ if(sqlGeometry.IsCurvePolygon())
+ {
+ if (elementIndex != 1)
+ Ext.ThrowInvalidElementIndex();
+ if (ringIndex == 0)
+ return sqlGeometry;
+ // re-assign sub component; if it is a COMPOUND CURVE
+ var subComponent = ringIndex == 1 ? sqlGeometry.STExteriorRing() : sqlGeometry.STInteriorRingN(ringIndex - 1); // subtracting exterior ring count
+ if (subComponent.IsCompoundCurve())
+ {
+ sqlGeometry = subComponent;
+ elementIndex = 1;
+ ringIndex = 1;
+ }
+ }
+
+ var isSimpleType = sqlGeometry.IsPoint() || sqlGeometry.IsLineString() || sqlGeometry.IsCircularString();
+
+ // if simple type then return input geometry when index is 1 or 0
+ if (isSimpleType)
+ {
+ if (elementIndex != 1)
+ Ext.ThrowInvalidElementIndex();
+
+ if (ringIndex > 1)
+ Ext.ThrowInvalidSubElementIndex();
+
+ if (isSimpleType && ringIndex <= 1)
+ return sqlGeometry;
+ }
+
+ // MULTIPOINT
+ if (sqlGeometry.IsMultiPoint())
+ {
+ if (elementIndex != 1)
+ Ext.ThrowInvalidElementIndex();
+
+ if (ringIndex == 0)
+ return sqlGeometry;
+
+ var obtainedGeom = sqlGeometry.STGeometryN(ringIndex);
+ if (obtainedGeom == null || obtainedGeom.IsNull)
+ Ext.ThrowInvalidSubElementIndex();
+
+ return obtainedGeom;
+ }
+
+ // MULTILINESTRING
+ if (sqlGeometry.IsMultiLineString())
+ {
+ if (elementIndex > sqlGeometry.STNumGeometries())
+ Ext.ThrowInvalidElementIndex();
+
+ if (ringIndex > 1)
+ Ext.ThrowInvalidSubElementIndex();
+
+ return sqlGeometry.STGeometryN(elementIndex);
+ }
+
+ // COMPOUND CURVE
+ if (sqlGeometry.IsCompoundCurve())
+ {
+ if (elementIndex != 1)
+ Ext.ThrowInvalidElementIndex();
+
+ if (ringIndex > 1)
+ Ext.ThrowInvalidSubElementIndex();
+
+ return sqlGeometry;
+ }
+
+ // POLYGON and CURVEPOLYGON
+ if (sqlGeometry.IsPolygon() || sqlGeometry.IsCurvePolygon())
+ {
+ if (elementIndex != 1)
+ Ext.ThrowInvalidElementIndex();
+
+ // if sub element index is zero then return the input geometry
+ if (ringIndex == 0)
+ return sqlGeometry;
+
+ if (ringIndex > sqlGeometry.STNumInteriorRing() + 1)
+ Ext.ThrowInvalidSubElementIndex();
+
+ return GetPolygon(sqlGeometry, ringIndex);
+ }
+
+ // MULTIPOLYGON
+ if (sqlGeometry.IsMultiPolygon())
+ {
+ if (elementIndex == 0 || elementIndex > sqlGeometry.STNumGeometries())
+ Ext.ThrowInvalidElementIndex();
+
+ sqlGeometry = sqlGeometry.STGeometryN(elementIndex);
+
+ // if sub element index is zero then return the input geometry
+ if (ringIndex == 0)
+ return sqlGeometry;
+
+ if (ringIndex > sqlGeometry.STNumInteriorRing() + 1)
+ Ext.ThrowInvalidSubElementIndex();
+
+ return GetPolygon(sqlGeometry, ringIndex);
+ }
+
+ return sqlGeometry;
+ }
+
+ ///
+ /// Build Polygon from Linestring
+ ///
+ /// Input Sql Geometry
+ /// Polygon Ring Index
+ /// Is Polygon type is CurvePolygon
+ ///
+ private static SqlGeometry GetPolygon(SqlGeometry sqlGeometry, int ringIndex)
+ {
+ if (ringIndex == 1)
+ sqlGeometry = sqlGeometry.STExteriorRing();
+ else
+ sqlGeometry = sqlGeometry.STInteriorRingN(ringIndex - 1);
+
+ var polygonBuilder = new SqlGeometryBuilder();
+ var polygonSink = new ExtractPolygonFromLineGeometrySink(polygonBuilder, ringIndex != 1);
+ sqlGeometry.Populate(polygonSink);
+ return polygonBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Utility method for converting Polygon types to LineString types.
+ ///
+ /// The Input SqlGeometry
+ /// SqlGeometry
+ public static SqlGeometry PolygonToLine(SqlGeometry geometry)
+ {
+ // Do manipulation only if it a polygon type
+ // else return the input geometry as is
+ if (geometry.IsPolygon(false))
+ return geometry.GetLineWKTFromPolygon().GetGeom(geometry.STSrid);
+ else if (geometry.IsMultiPolygon(false))
+ return geometry.GetLineWKTFromMultiPolygon().GetGeom(geometry.STSrid);
+ else if (geometry.IsCurvePolygon(false))
+ return geometry.GetLineWKTFromCurvePolygon().GetGeom(geometry.STSrid);
+ else
+ return geometry;
+ }
+
+ ///
+ /// Utility Method for removing the consecutive duplicate vertices
+ ///
+ ///
+ ///
+ ///
+ public static SqlGeometry RemoveDuplicateVertices(SqlGeometry geometry, double tolerance = Constants.Tolerance)
+ {
+ if(!geometry.STIsValid())
+ Ext.ThrowIfInvalidGeometry();
+
+ return geometry.Reduce(tolerance);
+ }
+ }
+}
diff --git a/src/KMLProcessor/Constants.cs b/src/KMLProcessor/Constants.cs
new file mode 100644
index 0000000..ae643fc
--- /dev/null
+++ b/src/KMLProcessor/Constants.cs
@@ -0,0 +1,32 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+namespace SQLSpatialTools.KMLProcessor
+{
+ public static class Constants
+ {
+ ///
+ /// SQL Server default SRID. SQL Server uses the default SRID of 4326,
+ /// which maps to the WGS 84 spatial reference system, when using methods on geography instances.
+ ///
+ /// Source: http://msdn.microsoft.com/en-us/library/bb964707.aspx
+ ///
+ public static int DefaultSRID => 4326;
+
+ ///
+ /// Google's KML extensions namespace
+ ///
+ public static string GxNamespace => "http://www.google.com/kml/ext/2.2";
+
+ ///
+ /// KML namespace
+ ///
+ public static string KmlNamespace => "http://www.opengis.net/kml/2.2";
+
+ ///
+ /// Atom namespace
+ ///
+ public static string AtomNamespace => "http://www.w3.org/2005/Atom";
+ }
+}
diff --git a/KMLProcessor/Export/ExportContext.cs b/src/KMLProcessor/Export/ExportContext.cs
similarity index 77%
rename from KMLProcessor/Export/ExportContext.cs
rename to src/KMLProcessor/Export/ExportContext.cs
index 7933c28..a30a31c 100644
--- a/KMLProcessor/Export/ExportContext.cs
+++ b/src/KMLProcessor/Export/ExportContext.cs
@@ -1,137 +1,132 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class will keep track of the context where some action will be executed
- /// during the KML export process. This class will keep track of the number of figures previously
- /// started in a spatial object, it will also keep track of the spatial object nesting level and the
- /// type of each opened/started spatial object
- /// Type of the spatial object: OpenGisGeographyType or OpenGisGeometryType
- ///
- internal class ExportContext
- {
- #region Private Data
-
- ///
- /// This list will simulate the stack of spatial object types. When a spatial object instance
- /// is visited, its type will be stored on the end of this list, and the visit will
- /// proceed on its child objects. So this list will store the types of all
- /// parents of the currently visited spatial object. When a visit to a spatial object is finished,
- /// the type of that object will be removed from the end of this list.
- ///
- private List m_Type = new List();
-
- ///
- /// This structure is equivalent to the m_Type structure, except it will store the information
- /// about the number of figures which are visited in each spatial object.
- ///
- private List m_Figures = new List();
-
- ///
- /// Represents the depth in the spatial object tree. It will show the distance from the currently
- /// spatial object to the root spatial object.
- ///
- private int m_Depth = 0;
-
- #endregion
-
- #region Public Methods
-
- ///
- /// This method should be called when a visit to the spatial object is started.
- /// The context will be updated with the information about the given spatial object type.
- ///
- /// Type of spatial object
- public void BeginSpatialObject(T type)
- {
- m_Depth += 1; // Increment the tree depth
-
- // Add information about this spatial object in the stack of visited spatial objects
- if (m_Depth > m_Type.Count)
- {
- m_Type.Add(type);
- m_Figures.Add(0);
- }
- else
- {
- m_Type[m_Depth - 1] = type;
- m_Figures[m_Depth - 1] = 0;
- }
- }
-
- ///
- /// This method should be called when the visit to spatial object is finished
- ///
- public void EndSpatialObject()
- {
- m_Depth -= 1;
- }
-
- ///
- /// This method should be called when new figure visit is started
- ///
- public void BeginFigure()
- {
- m_Figures[m_Depth - 1] += 1;
- }
-
- ///
- /// This method should be called when a figure visit is finished
- ///
- public void EndFigure()
- {
- // Nothing to be done. We calculates the total number of figures inside the spatial object,
- // so we don't need to decrease the counter
- }
-
- #endregion
-
- #region Public Properties
-
- ///
- /// The type of spatial object which is currently being processed
- ///
- public T Type
- {
- get
- {
- if (m_Depth == 0 || m_Depth > m_Type.Count)
- throw new KMLException("Type tree is invalid!");
-
- return m_Type[m_Depth - 1];
- }
- }
-
- ///
- /// True if the figure which is currently being processed is the first figure in
- /// the current spatial object
- ///
- public bool IsFirstFigure
- {
- get
- {
- return Figures == 1;
- }
- }
-
- ///
- /// The number of visited figures in the current spatial object
- ///
- public int Figures
- {
- get
- {
- if (m_Depth == 0 || m_Depth > m_Figures.Count)
- throw new KMLException("Figure tree is invalid!");
-
- return m_Figures[m_Depth - 1];
- }
- }
-
- #endregion
- }
-}
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+
+namespace SQLSpatialTools.KMLProcessor.Export
+{
+ ///
+ /// This class will keep track of the context where some action will be executed
+ /// during the KML export process. This class will keep track of the number of figures previously
+ /// started in a spatial object, it will also keep track of the spatial object nesting level and the
+ /// type of each opened/started spatial object
+ /// Type of the spatial object: OpenGisGeographyType or OpenGisGeometryType
+ ///
+ internal class ExportContext
+ {
+ #region Private Data
+
+ ///
+ /// This list will simulate the stack of spatial object types. When a spatial object instance
+ /// is visited, its type will be stored on the end of this list, and the visit will
+ /// proceed on its child objects. So this list will store the types of all
+ /// parents of the currently visited spatial object. When a visit to a spatial object is finished,
+ /// the type of that object will be removed from the end of this list.
+ ///
+ private readonly List _type = new List();
+
+ ///
+ /// This structure is equivalent to the m_Type structure, except it will store the information
+ /// about the number of figures which are visited in each spatial object.
+ ///
+ private readonly List _figures = new List();
+
+ ///
+ /// Represents the depth in the spatial object tree. It will show the distance from the currently
+ /// spatial object to the root spatial object.
+ ///
+ private int _depth;
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// This method should be called when a visit to the spatial object is started.
+ /// The context will be updated with the information about the given spatial object type.
+ ///
+ /// Type of spatial object
+ public void BeginSpatialObject(T type)
+ {
+ _depth += 1; // Increment the tree depth
+
+ // Add information about this spatial object in the stack of visited spatial objects
+ if (_depth > _type.Count)
+ {
+ _type.Add(type);
+ _figures.Add(0);
+ }
+ else
+ {
+ _type[_depth - 1] = type;
+ _figures[_depth - 1] = 0;
+ }
+ }
+
+ ///
+ /// This method should be called when the visit to spatial object is finished
+ ///
+ public void EndSpatialObject()
+ {
+ _depth -= 1;
+ }
+
+ ///
+ /// This method should be called when new figure visit is started
+ ///
+ public void BeginFigure()
+ {
+ _figures[_depth - 1] += 1;
+ }
+
+ ///
+ /// This method should be called when a figure visit is finished
+ ///
+ public void EndFigure()
+ {
+ // Nothing to be done. We calculates the total number of figures inside the spatial object,
+ // so we don't need to decrease the counter
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// The type of spatial object which is currently being processed
+ ///
+ public T Type
+ {
+ get
+ {
+ if (_depth == 0 || _depth > _type.Count)
+ throw new KMLException("Type tree is invalid!");
+
+ return _type[_depth - 1];
+ }
+ }
+
+ ///
+ /// True if the figure which is currently being processed is the first figure in
+ /// the current spatial object
+ ///
+ public bool IsFirstFigure => Figures == 1;
+
+ ///
+ /// The number of visited figures in the current spatial object
+ ///
+ public int Figures
+ {
+ get
+ {
+ if (_depth == 0 || _depth > _figures.Count)
+ throw new KMLException("Figure tree is invalid!");
+
+ return _figures[_depth - 1];
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/KMLProcessor/Export/KeyholeMarkupLanguageBase.cs b/src/KMLProcessor/Export/KeyholeMarkupLanguageBase.cs
similarity index 67%
rename from KMLProcessor/Export/KeyholeMarkupLanguageBase.cs
rename to src/KMLProcessor/Export/KeyholeMarkupLanguageBase.cs
index 6048071..97b6cfd 100644
--- a/KMLProcessor/Export/KeyholeMarkupLanguageBase.cs
+++ b/src/KMLProcessor/Export/KeyholeMarkupLanguageBase.cs
@@ -1,120 +1,119 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// The base class for all KML exporters
- ///
- public class KeyholeMarkupLanguageBase
- {
- #region Constructors
-
- ///
- /// Constructor. Creates a KeyholeMarkupLanguageBase object which will fill
- /// the given xml writer with a spatial data in the KML format
- ///
- /// The Xml writter to be filled with a spatial data in the KML format
- public KeyholeMarkupLanguageBase(System.Xml.XmlWriter writer)
- {
- _writer = writer;
-
- BeginKMLDocument();
- }
-
- #endregion
-
- #region Protected helper methods
-
- ///
- /// This method will create the necessary start elements in the KML file
- ///
- protected void BeginKMLDocument()
- {
- // Creates the beginning of the KML file as:
-
- _writer.WriteStartElement("kml", Constants.KmlNamespace);
- _writer.WriteAttributeString("xmlns", "gx", null, Constants.GxNamespace);
- _writer.WriteAttributeString("xmlns", "kml", null, Constants.KmlNamespace);
- _writer.WriteAttributeString("xmlns", "atom", null, Constants.AtomNamespace);
-
- StartElement("Document"); //
- StartElement("name"); //
- WriteString("Generated document"); // Generated document
- EndElement(); //
-
- StartElement("Placemark"); //
- StartElement("name"); //
- WriteString("Placemark exported from sql spatial"); // Placemark exported from sql spatial
- EndElement(); //
- }
-
- ///
- /// This method will start a tag for xml element with the given name
- ///
- /// Xml element name to be created
- protected void StartElement(string name)
- {
- _writer.WriteStartElement(name);
- }
-
- ///
- /// This method will close the last opened (and not closed) element. (A end tag will be added)
- ///
- protected void EndElement()
- {
- _writer.WriteEndElement();
- }
-
- ///
- /// This method will write the given text into the xml writter
- ///
- /// The text to be written
- protected void WriteString(string text)
- {
- _writer.WriteString(text);
- }
-
- #endregion
-
- #region Public Methods
-
- ///
- /// This method will close this sink. It will close all xml elements which
- /// were created during sink population.
- ///
- public void FinalizeKMLDocument()
- {
- EndElement(); //
- EndElement(); //
- EndElement(); //
- }
-
- ///
- /// This method will set the spatial reference system identifier (SrId)
- ///
- /// Spatial reference system identifier (SrId)
- public void SetSrid(int srid)
- {
- m_SrId = srid;
- }
-
- #endregion
-
- #region Protected Data
-
- ///
- /// Xml writter to be filled with data
- ///
- protected System.Xml.XmlWriter _writer;
-
- ///
- /// Spatial reference system identifier (SrId) for this file
- ///
- protected int m_SrId;
-
- #endregion
- }
-}
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+namespace SQLSpatialTools.KMLProcessor.Export
+{
+ ///
+ /// The base class for all KML exporters
+ ///
+ public class KeyholeMarkupLanguageBase
+ {
+ #region Constructors
+
+ ///
+ /// Constructor. Creates a KeyholeMarkupLanguageBase object which will fill
+ /// the given xml writer with a spatial data in the KML format
+ ///
+ /// The Xml writer to be filled with a spatial data in the KML format
+ protected KeyholeMarkupLanguageBase(System.Xml.XmlWriter writer)
+ {
+ Writer = writer;
+
+ BeginKMLDocument();
+ }
+
+ #endregion
+
+ #region Protected helper methods
+
+ ///
+ /// This method will create the necessary start elements in the KML file
+ ///
+ private void BeginKMLDocument()
+ {
+ // Creates the beginning of the KML file as:
+
+ Writer.WriteStartElement("kml", Constants.KmlNamespace);
+ Writer.WriteAttributeString("xmlns", "gx", null, Constants.GxNamespace);
+ Writer.WriteAttributeString("xmlns", "kml", null, Constants.KmlNamespace);
+ Writer.WriteAttributeString("xmlns", "atom", null, Constants.AtomNamespace);
+
+ StartElement("Document"); //
+ StartElement("name"); //
+ WriteString("Generated document"); // Generated document
+ EndElement(); //
+
+ StartElement("Placemark"); //
+ StartElement("name"); //
+ WriteString("Placemark exported from sql spatial"); // Placemark exported from sql spatial
+ EndElement(); //
+ }
+
+ ///
+ /// This method will start a tag for xml element with the given name
+ ///
+ /// Xml element name to be created
+ protected void StartElement(string name)
+ {
+ Writer.WriteStartElement(name);
+ }
+
+ ///
+ /// This method will close the last opened (and not closed) element. (A end tag will be added)
+ ///
+ protected void EndElement()
+ {
+ Writer.WriteEndElement();
+ }
+
+ ///
+ /// This method will write the given text into the xml writer
+ ///
+ /// The text to be written
+ protected void WriteString(string text)
+ {
+ Writer.WriteString(text);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// This method will close this sink. It will close all xml elements which
+ /// were created during sink population.
+ ///
+ public void FinalizeKMLDocument()
+ {
+ EndElement(); //
+ EndElement(); //
+ EndElement(); //
+ }
+
+ ///
+ /// This method will set the spatial reference system identifier (SrId)
+ ///
+ /// Spatial reference system identifier (SrId)
+ public void SetSrid(int srid)
+ {
+ SRID = srid;
+ }
+
+ #endregion
+
+ #region Protected Data
+
+ ///
+ /// Xml writer to be filled with data
+ ///
+ protected readonly System.Xml.XmlWriter Writer;
+
+ ///
+ /// Spatial reference system identifier (SrId) for this file
+ ///
+ protected int SRID;
+
+ #endregion
+ }
+}
diff --git a/KMLProcessor/Export/KeyholeMarkupLanguageGeography.cs b/src/KMLProcessor/Export/KeyholeMarkupLanguageGeography.cs
similarity index 55%
rename from KMLProcessor/Export/KeyholeMarkupLanguageGeography.cs
rename to src/KMLProcessor/Export/KeyholeMarkupLanguageGeography.cs
index 005b800..95e1acc 100644
--- a/KMLProcessor/Export/KeyholeMarkupLanguageGeography.cs
+++ b/src/KMLProcessor/Export/KeyholeMarkupLanguageGeography.cs
@@ -1,239 +1,247 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class is the geography sink. It will export the given geography instances
- /// into the KML format.
- ///
- internal class KeyholeMarkupLanguageGeography : KeyholeMarkupLanguageBase, IGeographySink
- {
- #region Constructors
-
- ///
- /// Constructor. Creates a KeyholeMarkupLanguageGeography sink which will fill the given
- /// xml writer with data in the KML format
- ///
- /// Xml writter to be filled with data
- public KeyholeMarkupLanguageGeography(System.Xml.XmlWriter writer)
- : base(writer)
- {
- }
-
- #endregion
-
- #region IGeographySink interface
-
- ///
- /// This method will be called when a new geography instance should be passed to the sink
- ///
- /// Geography type
- public void BeginGeography(OpenGisGeographyType type)
- {
- m_Context.BeginSpatialObject(type);
-
- switch (type)
- {
- case OpenGisGeographyType.Point:
- {
- StartElement("Point");
- break;
- }
- case OpenGisGeographyType.LineString:
- {
- StartElement("LineString");
- break;
- }
- case OpenGisGeographyType.Polygon:
- {
- StartElement("Polygon");
- break;
- }
- case OpenGisGeographyType.MultiPoint:
- case OpenGisGeographyType.MultiLineString:
- case OpenGisGeographyType.MultiPolygon:
- case OpenGisGeographyType.GeometryCollection:
- {
- StartElement("MultiGeometry");
- break;
- }
- default:
- {
- throw new KMLException("Type not supported: " + type.ToString());
- }
- }
- }
-
- ///
- /// This method will be called when the geography instance should be finalized
- ///
- public void EndGeography()
- {
- m_Context.EndSpatialObject();
-
- EndElement();
- }
-
- ///
- /// This method will be called when a new figure is passed to sink
- ///
- /// X coordinate
- /// Y coordinate
- /// Z coordinate
- /// M coordinate
- public void BeginFigure(double x, double y, Nullable z, Nullable m)
- {
- m_Context.BeginFigure();
-
- #region Export of Altitude Mode flag
-
- if ((m_Context.Type == OpenGisGeographyType.Polygon && m_Context.IsFirstFigure) ||
- m_Context.Type == OpenGisGeographyType.Point ||
- m_Context.Type == OpenGisGeographyType.LineString)
- {
- // The following code exports the geography instance's altitude mode.
- // Altitude mode is stored as the m coordinate of every point,
- // but altitude mode is a geography instance property, so it must be the same for all vertices,
- // and we take it from the m coordinate of the first point.
-
- if (m.HasValue)
- {
- int altitudeModeCode = (int)m.Value;
- if (Enum.IsDefined(typeof(AltitudeMode), altitudeModeCode))
- {
- AltitudeMode altitudeMode = (AltitudeMode)altitudeModeCode;
-
- switch (altitudeMode)
- {
- case AltitudeMode.absolute:
- case AltitudeMode.relativeToGround:
- case AltitudeMode.relativeToSeaFloor:
- {
- StartElement("altitudeMode");
- WriteString(altitudeMode.ToString());
- EndElement();
- break;
- }
- case AltitudeMode.clampToGround:
- case AltitudeMode.clampToSeaFloor:
- {
- _writer.WriteStartElement("altitudeMode", Constants.GxNamespace);
- WriteString(altitudeMode.ToString());
- _writer.WriteEndElement();
- break;
- }
- default:
- {
- throw new KMLException("Altitude mode not supported: " + altitudeMode.ToString());
- }
- }
- }
- }
- }
-
- #endregion
-
- #region Export of Tesselate Flag
-
- // If the altitude mode is "clamp to ground" or "clamp to sea floor" then a tesselate flag will be exported
-
- if (m_Context.Type == OpenGisGeographyType.LineString ||
- (m_Context.Type == OpenGisGeographyType.Polygon && m_Context.IsFirstFigure))
- {
- if (m.HasValue)
- {
- int altitudeModeCode = (int)m.Value;
- if (Enum.IsDefined(typeof(AltitudeMode), altitudeModeCode))
- {
- AltitudeMode altitudeMode = (AltitudeMode)altitudeModeCode;
- if (altitudeMode == AltitudeMode.clampToGround ||
- altitudeMode == AltitudeMode.clampToSeaFloor)
- {
- StartElement("tessellate");
- WriteString("1");
- EndElement();
- }
- }
- }
- }
-
- #endregion
-
- if (m_Context.Type == OpenGisGeographyType.Point ||
- m_Context.Type == OpenGisGeographyType.LineString)
- {
- StartElement("coordinates");
- }
- else if (m_Context.Type == OpenGisGeographyType.Polygon)
- {
- StartElement(m_Context.IsFirstFigure ? "outerBoundaryIs" : "innerBoundaryIs");
- StartElement("LinearRing");
- StartElement("coordinates");
- }
-
- _writer.WriteValue(Utilities.ShiftInRange(y, 180));
- _writer.WriteValue(",");
- _writer.WriteValue(Utilities.ShiftInRange(x, 90));
- if (z != null && z.HasValue)
- {
- _writer.WriteValue(",");
- _writer.WriteValue(z.Value);
- }
- }
-
- ///
- /// This method should be called when a figure is finalized
- ///
- public void EndFigure()
- {
- m_Context.EndFigure();
-
- if (m_Context.Type == OpenGisGeographyType.Point ||
- m_Context.Type == OpenGisGeographyType.LineString)
- {
- EndElement();
- }
- else if (m_Context.Type == OpenGisGeographyType.Polygon)
- {
- EndElement(); // Closing coordinates tag
- EndElement(); // Closing LinearRing tag
- EndElement(); // Closing outerBoundaryIs / innerBoundaryIs tag
- }
- }
-
- ///
- /// This method should be called when a new line is passed to the sink
- ///
- /// X coordinate
- /// Y coordinate
- /// Z coordinate
- /// M coordinate
- public void AddLine(double x, double y, Nullable z, Nullable m)
- {
- _writer.WriteValue("\r\n");
-
- _writer.WriteValue(Utilities.ShiftInRange(y, 180));
- _writer.WriteValue(",");
- _writer.WriteValue(Utilities.ShiftInRange(x, 90));
- if (z != null && z.HasValue)
- {
- _writer.WriteValue(",");
- _writer.WriteValue(z.Value);
- }
- }
-
- #endregion
-
- #region Private Data
-
- ///
- /// Export execution context
- ///
- private ExportContext m_Context = new ExportContext();
-
- #endregion
- }
-}
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.KMLProcessor.Import;
+
+namespace SQLSpatialTools.KMLProcessor.Export
+{
+ ///
+ /// This class is the geography sink. It will export the given geography instances
+ /// into the KML format.
+ ///
+ internal class KeyholeMarkupLanguageGeography : KeyholeMarkupLanguageBase, IGeographySink110
+ {
+ #region Constructors
+
+ ///
+ /// Constructor. Creates a KeyholeMarkupLanguageGeography sink which will fill the given
+ /// xml writer with data in the KML format
+ ///
+ /// Xml writer to be filled with data
+ public KeyholeMarkupLanguageGeography(System.Xml.XmlWriter writer)
+ : base(writer)
+ {
+ }
+
+ #endregion
+
+ #region IGeographySink110 interface
+
+ ///
+ /// This method will be called when a new geography instance should be passed to the sink
+ ///
+ /// Geography type
+ public void BeginGeography(OpenGisGeographyType type)
+ {
+ _context.BeginSpatialObject(type);
+
+ switch (type)
+ {
+ case OpenGisGeographyType.Point:
+ {
+ StartElement("Point");
+ break;
+ }
+ case OpenGisGeographyType.LineString:
+ {
+ StartElement("LineString");
+ break;
+ }
+ case OpenGisGeographyType.Polygon:
+ {
+ StartElement("Polygon");
+ break;
+ }
+ case OpenGisGeographyType.MultiPoint:
+ case OpenGisGeographyType.MultiLineString:
+ case OpenGisGeographyType.MultiPolygon:
+ case OpenGisGeographyType.GeometryCollection:
+ {
+ StartElement("MultiGeometry");
+ break;
+ }
+ default:
+ {
+ throw new KMLException("Type not supported: " + type.ToString());
+ }
+ }
+ }
+
+ ///
+ /// This method will be called when the geography instance should be finalized
+ ///
+ public void EndGeography()
+ {
+ _context.EndSpatialObject();
+
+ EndElement();
+ }
+
+ ///
+ /// This method will be called when a new figure is passed to sink
+ ///
+ /// X coordinate
+ /// Y coordinate
+ /// Z coordinate
+ /// M coordinate
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _context.BeginFigure();
+
+ #region Export of Altitude Mode flag
+
+ if ((_context.Type == OpenGisGeographyType.Polygon && _context.IsFirstFigure) ||
+ _context.Type == OpenGisGeographyType.Point ||
+ _context.Type == OpenGisGeographyType.LineString)
+ {
+ // The following code exports the geography instance's altitude mode.
+ // Altitude mode is stored as the m coordinate of every point,
+ // but altitude mode is a geography instance property, so it must be the same for all vertices,
+ // and we take it from the m coordinate of the first point.
+
+ if (m.HasValue)
+ {
+ var altitudeModeCode = (int)m.Value;
+ if (Enum.IsDefined(typeof(AltitudeMode), altitudeModeCode))
+ {
+ var altitudeMode = (AltitudeMode)altitudeModeCode;
+
+ switch (altitudeMode)
+ {
+ case AltitudeMode.Absolute:
+ case AltitudeMode.RelativeToGround:
+ case AltitudeMode.RelativeToSeaFloor:
+ {
+ StartElement("altitudeMode");
+ WriteString(altitudeMode.ToString());
+ EndElement();
+ break;
+ }
+ case AltitudeMode.ClampToGround:
+ case AltitudeMode.ClampToSeaFloor:
+ {
+ Writer.WriteStartElement("altitudeMode", Constants.GxNamespace);
+ WriteString(altitudeMode.ToString());
+ Writer.WriteEndElement();
+ break;
+ }
+ default:
+ {
+ throw new KMLException("Altitude mode not supported: " + altitudeMode.ToString());
+ }
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region Export of Tessellate Flag
+
+ // If the altitude mode is "clamp to ground" or "clamp to sea floor" then a tessellate flag will be exported
+
+ if (_context.Type == OpenGisGeographyType.LineString ||
+ (_context.Type == OpenGisGeographyType.Polygon && _context.IsFirstFigure))
+ {
+ if (m.HasValue)
+ {
+ var altitudeModeCode = (int)m.Value;
+ if (Enum.IsDefined(typeof(AltitudeMode), altitudeModeCode))
+ {
+ var altitudeMode = (AltitudeMode)altitudeModeCode;
+ if (altitudeMode == AltitudeMode.ClampToGround ||
+ altitudeMode == AltitudeMode.ClampToSeaFloor)
+ {
+ StartElement("tessellate");
+ WriteString("1");
+ EndElement();
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ if (_context.Type == OpenGisGeographyType.Point ||
+ _context.Type == OpenGisGeographyType.LineString)
+ {
+ StartElement("coordinates");
+ }
+ else if (_context.Type == OpenGisGeographyType.Polygon)
+ {
+ StartElement(_context.IsFirstFigure ? "outerBoundaryIs" : "innerBoundaryIs");
+ StartElement("LinearRing");
+ StartElement("coordinates");
+ }
+
+ Writer.WriteValue(Utilities.ShiftInRange(y, 180));
+ Writer.WriteValue(",");
+ Writer.WriteValue(Utilities.ShiftInRange(x, 90));
+ if (z.HasValue)
+ {
+ Writer.WriteValue(",");
+ Writer.WriteValue(z.Value);
+ }
+ }
+
+ ///
+ /// This method should be called when a figure is finalized
+ ///
+ public void EndFigure()
+ {
+ _context.EndFigure();
+
+ if (_context.Type == OpenGisGeographyType.Point ||
+ _context.Type == OpenGisGeographyType.LineString)
+ {
+ EndElement();
+ }
+ else if (_context.Type == OpenGisGeographyType.Polygon)
+ {
+ EndElement(); // Closing coordinates tag
+ EndElement(); // Closing LinearRing tag
+ EndElement(); // Closing outerBoundaryIs / innerBoundaryIs tag
+ }
+ }
+
+ ///
+ /// This method should be called when a new line is passed to the sink
+ ///
+ /// X coordinate
+ /// Y coordinate
+ /// Z coordinate
+ /// M coordinate
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ Writer.WriteValue("\r\n");
+
+ Writer.WriteValue(Utilities.ShiftInRange(y, 180));
+ Writer.WriteValue(",");
+ Writer.WriteValue(Utilities.ShiftInRange(x, 90));
+ if (z.HasValue)
+ {
+ Writer.WriteValue(",");
+ Writer.WriteValue(z.Value);
+ }
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ #endregion
+
+ #region Private Data
+
+ ///
+ /// Export execution context
+ ///
+ private readonly ExportContext _context = new ExportContext();
+
+ #endregion
+ }
+}
diff --git a/KMLProcessor/Export/KeyholeMarkupLanguageGeometry.cs b/src/KMLProcessor/Export/KeyholeMarkupLanguageGeometry.cs
similarity index 62%
rename from KMLProcessor/Export/KeyholeMarkupLanguageGeometry.cs
rename to src/KMLProcessor/Export/KeyholeMarkupLanguageGeometry.cs
index e5aa6fc..437efcd 100644
--- a/KMLProcessor/Export/KeyholeMarkupLanguageGeometry.cs
+++ b/src/KMLProcessor/Export/KeyholeMarkupLanguageGeometry.cs
@@ -1,166 +1,174 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.SqlServer.Types;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// This class is the geometry sink. It will export the given geometry instances
- /// into the KML format.
- ///
- internal class KeyholeMarkupLanguageGeometry : KeyholeMarkupLanguageBase, IGeometrySink
- {
- #region Constructors
-
- ///
- /// Constructor. Creates a KeyholeMarkupLanguageGeometry sink which will fill the given
- /// xml writer with data in the KML format
- ///
- /// Xml writter to be filled with data
- public KeyholeMarkupLanguageGeometry(System.Xml.XmlWriter writer)
- : base(writer)
- {
- }
-
- #endregion
-
- #region IGeometrySink interface
-
- ///
- /// This method will be called when a new geometry instance is passed to the sink
- ///
- /// Geometry type
- public void BeginGeometry(OpenGisGeometryType type)
- {
- m_Context.BeginSpatialObject(type);
-
- switch (type)
- {
- case OpenGisGeometryType.Point:
- {
- StartElement("Point");
- StartElement("coordinates");
- break;
- }
- case OpenGisGeometryType.LineString:
- {
- StartElement("LineString");
- StartElement("coordinates");
- break;
- }
- case OpenGisGeometryType.Polygon:
- {
- StartElement("Polygon");
- break;
- }
- case OpenGisGeometryType.MultiPoint:
- case OpenGisGeometryType.MultiLineString:
- case OpenGisGeometryType.MultiPolygon:
- case OpenGisGeometryType.GeometryCollection:
- {
- StartElement("MultiGeometry");
- break;
- }
- default:
- {
- throw new KMLException("Type not supported: " + type.ToString());
- }
- }
- }
-
- ///
- /// This method will be called when the geometry instance is finalized
- ///
- public void EndGeometry()
- {
- // Gets the type of the geometry instance which should be finalized
- OpenGisGeometryType type = m_Context.Type;
-
- m_Context.EndSpatialObject();
-
- if (type == OpenGisGeometryType.Point ||
- type == OpenGisGeometryType.LineString)
- {
- EndElement();
- }
-
- EndElement();
- }
-
- ///
- /// This method will be called when a new figure is passed to the sink
- ///
- /// X coordinate
- /// Y coordinate
- /// Z coordinate
- /// M coordinate
- public void BeginFigure(double x, double y, Nullable z, Nullable m)
- {
- m_Context.BeginFigure();
-
- if (m_Context.Type == OpenGisGeometryType.Polygon)
- {
- StartElement(m_Context.IsFirstFigure ? "outerBoundaryIs" : "innerBoundaryIs");
- StartElement("LinearRing");
- StartElement("coordinates");
- }
-
- _writer.WriteValue(y);
- _writer.WriteValue(",");
- _writer.WriteValue(x);
- if (z != null && z.HasValue)
- {
- _writer.WriteValue(",");
- _writer.WriteValue(z.Value);
- }
- }
-
- ///
- /// This method should be called when a figure is finalized
- ///
- public void EndFigure()
- {
- m_Context.EndFigure();
-
- if (m_Context.Type == OpenGisGeometryType.Polygon)
- {
- EndElement(); // Closing coordinates tag
- EndElement(); // Closing LinearRing tag
- EndElement(); // Closing outerBoundaryIs / innerBoundaryIs tag
- }
- }
-
- ///
- /// This method should be called when a new line is be passed to the sink
- ///
- /// X coordinate
- /// Y coordinate
- /// Z coordinate
- /// M coordinate
- public void AddLine(double x, double y, Nullable z, Nullable m)
- {
- _writer.WriteValue("\r\n");
-
- _writer.WriteValue(y);
- _writer.WriteValue(",");
- _writer.WriteValue(x);
- if (z != null && z.HasValue)
- {
- _writer.WriteValue(",");
- _writer.WriteValue(z.Value);
- }
- }
-
- #endregion
-
- #region Private Data
-
- ///
- /// Export execution context
- ///
- private ExportContext m_Context = new ExportContext();
-
- #endregion
- }
-}
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+// ReSharper disable UnusedMember.Global
+
+namespace SQLSpatialTools.KMLProcessor.Export
+{
+ ///
+ /// This class is the geometry sink. It will export the given geometry instances
+ /// into the KML format.
+ ///
+ internal class KeyholeMarkupLanguageGeometry : KeyholeMarkupLanguageBase, IGeometrySink110
+ {
+ #region Constructors
+
+ ///
+ /// Constructor. Creates a KeyholeMarkupLanguageGeometry sink which will fill the given
+ /// xml writer with data in the KML format
+ ///
+ /// Xml writer to be filled with data
+ public KeyholeMarkupLanguageGeometry(System.Xml.XmlWriter writer)
+ : base(writer)
+ {
+ }
+
+ #endregion
+
+ #region IGeometrySink interface
+
+ ///
+ /// This method will be called when a new geometry instance is passed to the sink
+ ///
+ /// Geometry type
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ _context.BeginSpatialObject(type);
+
+ switch (type)
+ {
+ case OpenGisGeometryType.Point:
+ {
+ StartElement("Point");
+ StartElement("coordinates");
+ break;
+ }
+ case OpenGisGeometryType.LineString:
+ {
+ StartElement("LineString");
+ StartElement("coordinates");
+ break;
+ }
+ case OpenGisGeometryType.Polygon:
+ {
+ StartElement("Polygon");
+ break;
+ }
+ case OpenGisGeometryType.MultiPoint:
+ case OpenGisGeometryType.MultiLineString:
+ case OpenGisGeometryType.MultiPolygon:
+ case OpenGisGeometryType.GeometryCollection:
+ {
+ StartElement("MultiGeometry");
+ break;
+ }
+ default:
+ {
+ throw new KMLException("Type not supported: " + type.ToString());
+ }
+ }
+ }
+
+ ///
+ /// This method will be called when the geometry instance is finalized
+ ///
+ public void EndGeometry()
+ {
+ // Gets the type of the geometry instance which should be finalized
+ var type = _context.Type;
+
+ _context.EndSpatialObject();
+
+ if (type == OpenGisGeometryType.Point ||
+ type == OpenGisGeometryType.LineString)
+ {
+ EndElement();
+ }
+
+ EndElement();
+ }
+
+ ///
+ /// This method will be called when a new figure is passed to the sink
+ ///
+ /// X coordinate
+ /// Y coordinate
+ /// Z coordinate
+ /// M coordinate
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _context.BeginFigure();
+
+ if (_context.Type == OpenGisGeometryType.Polygon)
+ {
+ StartElement(_context.IsFirstFigure ? "outerBoundaryIs" : "innerBoundaryIs");
+ StartElement("LinearRing");
+ StartElement("coordinates");
+ }
+
+ Writer.WriteValue(y);
+ Writer.WriteValue(",");
+ Writer.WriteValue(x);
+ if (z.HasValue)
+ {
+ Writer.WriteValue(",");
+ Writer.WriteValue(z.Value);
+ }
+ }
+
+ ///
+ /// This method should be called when a figure is finalized
+ ///
+ public void EndFigure()
+ {
+ _context.EndFigure();
+
+ if (_context.Type == OpenGisGeometryType.Polygon)
+ {
+ EndElement(); // Closing coordinates tag
+ EndElement(); // Closing LinearRing tag
+ EndElement(); // Closing outerBoundaryIs / innerBoundaryIs tag
+ }
+ }
+
+ ///
+ /// This method should be called when a new line is be passed to the sink
+ ///
+ /// X coordinate
+ /// Y coordinate
+ /// Z coordinate
+ /// M coordinate
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ Writer.WriteValue("\r\n");
+
+ Writer.WriteValue(y);
+ Writer.WriteValue(",");
+ Writer.WriteValue(x);
+ if (z.HasValue)
+ {
+ Writer.WriteValue(",");
+ Writer.WriteValue(z.Value);
+ }
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ #endregion
+
+ #region Private Data
+
+ ///
+ /// Export execution context
+ ///
+ private readonly ExportContext _context = new ExportContext();
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/FilterSetSridGeographySink.cs b/src/KMLProcessor/FilterSetSridGeographySink.cs
new file mode 100644
index 0000000..96db61d
--- /dev/null
+++ b/src/KMLProcessor/FilterSetSridGeographySink.cs
@@ -0,0 +1,80 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.KMLProcessor
+{
+ ///
+ /// This class will propagate all method calls to the given target sink,
+ /// except the method call to the SetSrid method.
+ ///
+ public class FilterSetSridGeographySink : IGeographySink110
+ {
+ #region Constructors
+
+ ///
+ /// Default Constructor
+ ///
+ /// Target sink
+ public FilterSetSridGeographySink(IGeographySink110 targetSink)
+ {
+ _targetSink = targetSink;
+ }
+
+ #endregion
+
+ #region Private Data
+
+ ///
+ /// Target sink to propagate all method calls to it, except the method call to the SetSrid method
+ ///
+ private readonly IGeographySink110 _targetSink;
+
+ #endregion
+
+ #region IGeographySink Members
+
+ public void AddLine(double latitude, double longitude, double? z, double? m)
+ {
+ _targetSink?.AddLine(latitude, longitude, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void BeginFigure(double latitude, double longitude, double? z, double? m)
+ {
+ _targetSink?.BeginFigure(latitude, longitude, z, m);
+ }
+
+ public void BeginGeography(OpenGisGeographyType type)
+ {
+ _targetSink?.BeginGeography(type);
+ }
+
+ public void EndFigure()
+ {
+ _targetSink?.EndFigure();
+ }
+
+ public void EndGeography()
+ {
+ _targetSink?.EndGeography();
+ }
+
+ ///
+ /// This method call will not be propagated to the target sink
+ ///
+ ///
+ public void SetSrid(int srid)
+ {
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/GeographyContext.cs b/src/KMLProcessor/GeographyContext.cs
new file mode 100644
index 0000000..569b1e2
--- /dev/null
+++ b/src/KMLProcessor/GeographyContext.cs
@@ -0,0 +1,31 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.KMLProcessor
+{
+ ///
+ /// This class contains the information about the context where a geography instance is found.
+ ///
+ public class GeographyContext
+ {
+ ///
+ /// Geography instance Id
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// Geography instance context. It contains the information about the parent and
+ /// the siblings of this geography instance.
+ /// It also contains the information about this geography instance.
+ ///
+ public string Context { get; set; }
+
+ ///
+ /// The extracted geography instance
+ ///
+ public SqlGeography Shape { get; set; }
+ }
+}
diff --git a/src/KMLProcessor/Import/AltitudeMode.cs b/src/KMLProcessor/Import/AltitudeMode.cs
new file mode 100644
index 0000000..6a0d3ef
--- /dev/null
+++ b/src/KMLProcessor/Import/AltitudeMode.cs
@@ -0,0 +1,18 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// Altitude modes allowed in KML
+ ///
+ public enum AltitudeMode
+ {
+ ClampToGround = 0,
+ RelativeToGround = 1,
+ Absolute = 2,
+ ClampToSeaFloor = 3,
+ RelativeToSeaFloor = 4
+ }
+}
diff --git a/src/KMLProcessor/Import/Geography.cs b/src/KMLProcessor/Import/Geography.cs
new file mode 100644
index 0000000..a6b0500
--- /dev/null
+++ b/src/KMLProcessor/Import/Geography.cs
@@ -0,0 +1,180 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using Geog = SQLSpatialTools.Functions.General.Geography;
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// Base class for all geography instances extracted from the KML file
+ ///
+ public abstract class Geography
+ {
+ #region Public Properties
+
+ ///
+ /// Id of the placemark which contains this geography instance
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// True if point(s) which are part of this geography instance
+ /// should be connected to the ground when they are visually represented in some visualization tool
+ ///
+ public bool Extrude { get; set; }
+
+ ///
+ /// True if all lines in this geography instance should follow the terrain.
+ /// This flag is not applicable just for Point instance.
+ ///
+ public bool Tessellate { get; set; }
+
+ ///
+ /// True if this geography instance is valid
+ ///
+ public bool IsValid
+ {
+ get
+ {
+ // This implementation is base on the property that the Sql Server 2008 (10.0 Katmai)
+ // will throw an exception if the geography instance is not valid.
+
+ try
+ {
+ var constructed = new SqlGeographyBuilder();
+ constructed.SetSrid(Constants.DefaultSRID);
+
+ Populate(constructed);
+
+ // ReSharper disable once UnusedVariable
+ var geography = constructed.ConstructedGeography;
+
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+ }
+
+ ///
+ /// The context where this geography instance is found. It will
+ /// contain the information about the parent and the siblings of this
+ /// geography instance, and the information about this geography instance
+ /// it self.
+ ///
+ public string Context { get; set; }
+
+ #endregion
+
+ #region Abstract Members
+
+ ///
+ /// This method populates the given sink with the data from this geography instance
+ ///
+ /// Sink to be populated
+ public abstract void Populate(IGeographySink110 sink);
+
+ ///
+ /// SqlGeography instance well-known text.
+ ///
+ public abstract string WKT
+ {
+ get;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// This method returns the geography instance that corresponds to this KML geography
+ ///
+ /// If true and this geography instance is invalid then the MakeValid
+ /// function will be executed on this geography instance
+ /// The geography instance that corresponds to this KML geography
+ public SqlGeography ToSqlGeography(bool makeValid)
+ {
+ if (IsValid)
+ {
+ var constructed = new SqlGeographyBuilder();
+ constructed.SetSrid(Constants.DefaultSRID);
+ Populate(constructed);
+ return constructed.ConstructedGeography;
+ }
+ if (makeValid)
+ {
+ var constructed = new SqlGeographyBuilder();
+ constructed.SetSrid(Constants.DefaultSRID);
+ MakeValid(constructed);
+ return constructed.ConstructedGeography;
+ }
+
+ throw new KMLException("Invalid geography instance.");
+ }
+
+ ///
+ /// This method returns a string representation of this object
+ ///
+ /// WKT for this geography instance
+ public override string ToString()
+ {
+ return WKT;
+ }
+
+ ///
+ /// This method populates the given sink with the data from this geography instance.
+ /// If this geography instance is invalid and the makeValid flag is set then a valid geography instance
+ /// will be constructed and the given sink will be populated with that instance.
+ ///
+ /// Sink to be populated
+ /// If true and this geography instance is invalid then the MakeValid
+ /// function will be executed on this geography instance
+ public void Populate(
+ IGeographySink110 sink,
+ bool makeValid)
+ {
+ if (makeValid)
+ {
+ if (IsValid)
+ Populate(sink);
+ else
+ MakeValid(sink);
+ }
+ else
+ {
+ Populate(sink);
+ }
+ }
+
+ ///
+ /// This method populates the given sink with the valid geography instance constructed from this geography instance.
+ ///
+ /// Sink to be populated
+ private void MakeValid(IGeographySink110 sink)
+ {
+ // 1. Creates the valid geography for this WKT
+ var vg = Geog.MakeValidGeographyFromText(WKT, Constants.DefaultSRID);
+
+ // 2. Populates the given sink with the valid geography
+ vg.Populate(new FilterSetSridGeographySink(sink));
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ ///
+ /// Id which was assigned to this geography instance when it was inserted in the database.
+ /// If the value is null then it is not stored in the database yet
+ ///
+ internal int? DbId { get; set; }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/GroundOverlay.cs b/src/KMLProcessor/Import/GroundOverlay.cs
new file mode 100644
index 0000000..c1d027a
--- /dev/null
+++ b/src/KMLProcessor/Import/GroundOverlay.cs
@@ -0,0 +1,80 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class holds the information about the ground overlay extracted from the KML file
+ ///
+ public class GroundOverlay : Geography
+ {
+ #region Public Properties
+
+ ///
+ /// Ground Overlay Name
+ ///
+ public string Name
+ {
+ get => _name;
+ internal set => _name = string.IsNullOrEmpty(value) ? "" : value;
+ }
+ ///
+ /// Data member for the Name property
+ ///
+ private string _name = "";
+
+ ///
+ /// Box defined inside this ground overlay.
+ ///
+ public Geography Box { private get; set; }
+
+ ///
+ /// Region defined inside this ground overlay.
+ ///
+ public Region Region { private get; set; }
+
+ ///
+ /// SqlGeography instance well-known text.
+ ///
+ public override string WKT
+ {
+ get
+ {
+ if (Box != null)
+ {
+ return Box.WKT;
+ }
+
+ if (Region != null)
+ {
+ return Region.WKT;
+ }
+
+ throw new KMLException("WKT is not defined.");
+ }
+ }
+
+ #endregion
+
+ #region Geography Methods
+
+ ///
+ /// This method populates the given sink with the data from this geography instance
+ ///
+ /// Sink to be populated
+ public override void Populate(Microsoft.SqlServer.Types.IGeographySink110 sink)
+ {
+ if (Box != null)
+ {
+ Box.Populate(sink);
+ }
+ else
+ {
+ Region?.Populate(sink);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/KMLParser.cs b/src/KMLProcessor/Import/KMLParser.cs
new file mode 100644
index 0000000..f6abb10
--- /dev/null
+++ b/src/KMLProcessor/Import/KMLParser.cs
@@ -0,0 +1,1128 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Xml;
+// ReSharper disable MemberCanBePrivate.Global
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class parses the given KML string
+ ///
+ public class KMLParser
+ {
+ #region Constructors
+
+ ///
+ /// Constructor. Accepts the string in the KML format.
+ ///
+ /// KML string to be parsed
+ public KMLParser(string kml)
+ {
+ Document = new XmlDocument();
+ Document.LoadXml(kml);
+ }
+
+ #endregion
+
+ #region Public Data
+
+ ///
+ /// Geography instances extracted from the KML string
+ ///
+ public IList Geographies { protected set; get; } = new List();
+
+ ///
+ /// Placemarks extracted from the KML string
+ ///
+ public IList Placemarks { protected set; get; } = new List();
+
+ ///
+ /// Models extracted from the KML string
+ ///
+ public IList Models { protected set; get; } = new List();
+
+ ///
+ /// Regions extracted from the KML string
+ ///
+ public IList Regions { protected set; get; } = new List();
+
+ ///
+ /// Ground overlays extracted from the KML string
+ ///
+ public IList GroundOverlays { protected set; get; } = new List();
+
+ #endregion
+
+ #region Protected Data
+
+ ///
+ /// Xml document whose data will be traversed
+ ///
+ protected readonly XmlDocument Document;
+
+ #endregion
+
+ #region Protected Methods
+
+ ///
+ /// This method visits the given KML node
+ ///
+ /// KML node to be visited
+ protected void Visit(XmlNode node)
+ {
+ if (node == null) return;
+
+ var nodeName = node.Name.ToLowerInvariant();
+
+ if (node is XmlElement elem)
+ {
+ if (nodeName.Equals("point"))
+ {
+ VisitPoint(elem);
+ }
+ else if (nodeName.Equals("linestring"))
+ {
+ VisitLineString(elem);
+ }
+ else if (nodeName.Equals("linearring"))
+ {
+ VisitLinearRing(elem);
+ }
+ else if (nodeName.Equals("polygon"))
+ {
+ VisitPolygon(elem);
+ }
+ else if (nodeName.Equals("multigeometry"))
+ {
+ VisitMultiGeometry(elem);
+ }
+ else if (nodeName.Equals("placemark"))
+ {
+ VisitPlacemark(elem);
+ }
+ else if (nodeName.Equals("model"))
+ {
+ VisitModel(elem);
+ }
+ else if (nodeName.Equals("region"))
+ {
+ VisitRegion(elem);
+ }
+ else if (nodeName.Equals("groundoverlay"))
+ {
+ VisitGroundOverlay(elem);
+ }
+ else
+ {
+ // Visits the child nodes
+ foreach (XmlNode child in node.ChildNodes)
+ {
+ Visit(child);
+ }
+ }
+ }
+ }
+
+ ///
+ /// This method visits a KML Ground Overlay element
+ ///
+ /// KML Ground Overlay element
+ protected void VisitGroundOverlay(XmlElement element)
+ {
+ if (element == null) return;
+
+ var groundOverlay = new GroundOverlay();
+
+ VisitGeography(element, groundOverlay);
+ groundOverlay.Name = GetChildElementText(element, "name");
+
+ var latLonAltBoxElem = GetFirstChild(element, "LatLonAltBox");
+ var latLonBoxElem = GetFirstChild(element, "LatLonBox");
+ var latLonQuadElem = GetFirstChild(element, "gx:LatLonQuad");
+
+ var numOfGeographiesBefore = Geographies.Count;
+
+ if (latLonAltBoxElem != null)
+ {
+ VisitLatLonAltBox(latLonAltBoxElem);
+ }
+ else if (latLonBoxElem != null)
+ {
+ VisitLatLonBox(latLonBoxElem);
+ }
+ else if (latLonQuadElem != null)
+ {
+ VisitLatLonQuad(latLonQuadElem);
+ }
+
+ var numOfGeographiesAfter = Geographies.Count;
+
+ if (numOfGeographiesAfter == numOfGeographiesBefore + 1)
+ {
+ groundOverlay.Box = Geographies[Geographies.Count - 1];
+ Geographies.RemoveAt(Geographies.Count - 1);
+ }
+ else if (numOfGeographiesAfter > numOfGeographiesBefore + 1)
+ {
+ // Multiple geography instances found in a single ground overlay.
+ // The last geography instance is the border.
+
+ groundOverlay.Box = Geographies[Geographies.Count - 1];
+ Geographies.RemoveAt(Geographies.Count - 1);
+ }
+
+ #region Check if a region is also defined inside this ground overlay
+
+ var regionElem = GetFirstChild(element, "Region");
+ if (regionElem != null)
+ {
+ var numOfRegionsBefore = Regions.Count;
+
+ VisitRegion(regionElem);
+
+ var numOfRegionsAfter = Regions.Count;
+
+ if (numOfRegionsAfter == numOfRegionsBefore + 1)
+ {
+ groundOverlay.Region = Regions[numOfRegionsAfter - 1];
+ }
+ else if (numOfRegionsAfter > numOfRegionsBefore + 1)
+ {
+ // Multiple regions found in a single ground overlay. The last region is the border.
+
+ groundOverlay.Region = Regions[numOfRegionsAfter - 1];
+ }
+ }
+
+ #endregion
+
+ GroundOverlays.Add(groundOverlay);
+ }
+
+ ///
+ /// This method visits a KML placemark element
+ ///
+ /// KML placemark element
+ protected void VisitPlacemark(XmlElement elem)
+ {
+ if (elem == null) return;
+
+ var placemark = new Placemark();
+
+ placemark.Id = GetAttribute(elem, "id");
+
+ // Visits the child nodes
+ foreach (XmlNode child in elem.ChildNodes)
+ {
+ var childName = child.Name.ToLowerInvariant();
+ if (childName.Equals("name"))
+ {
+ if (child.HasChildNodes)
+ {
+ placemark.Name = child.ChildNodes[0].InnerText;
+ }
+ }
+ else if (childName.Equals("description"))
+ {
+ if (child.HasChildNodes)
+ {
+ placemark.Description = child.ChildNodes[0].InnerText;
+ }
+ }
+ else if (childName.Equals("address"))
+ {
+ if (child.HasChildNodes)
+ {
+ placemark.Address = child.ChildNodes[0].InnerText;
+ }
+ }
+ else if (childName.Equals("lookat") && (child as XmlElement) != null)
+ {
+ var numOfGeographiesBefore = Geographies.Count;
+
+ VisitLookAt(child as XmlElement);
+
+ var numOfGeographiesAfter = Geographies.Count;
+
+ if (numOfGeographiesAfter > numOfGeographiesBefore)
+ {
+ // Point found where this placemark looks at
+
+ placemark.LookAt = Geographies[Geographies.Count - 1];
+ Geographies.RemoveAt(Geographies.Count - 1);
+ }
+ }
+ else
+ {
+ var numOfGeographiesBefore = Geographies.Count;
+
+ Visit(child);
+
+ var numOfGeographiesAfter = Geographies.Count;
+
+ if (numOfGeographiesAfter > numOfGeographiesBefore)
+ {
+ // Found the geography instance which describes this placemark
+
+ placemark.Geography = Geographies[Geographies.Count - 1];
+ }
+ }
+ }
+
+ Placemarks.Add(placemark);
+ }
+
+ ///
+ /// This method visits a LookAt element
+ ///
+ /// KML lookat element to be visited
+ protected void VisitLookAt(XmlElement elem)
+ {
+ if (elem == null) return;
+
+ VisitLonLatAltElement(elem);
+ }
+
+ ///
+ /// This method parses the given coordinates string.
+ ///
+ /// String with coordinates to be parsed
+ /// List of points contained in the given string
+ protected static IList ParseCoordinates(string coordinates)
+ {
+ var points = new List();
+
+ if (string.IsNullOrEmpty(coordinates) || coordinates.Trim().Length == 0)
+ return points;
+
+ // Removes the spaces inside the tuples
+ while (coordinates.Contains(", "))
+ {
+ coordinates = coordinates.Replace(", ", ",");
+ }
+
+ // Splits the tuples
+ var coordinate = coordinates.Trim().Split(' ', '\t', '\n');
+
+ // Process each tuple
+ foreach (var p in coordinate)
+ {
+ var tp = p.Trim();
+ if (tp.Length == 0)
+ continue;
+
+ var cords = tp.Split(',');
+
+ try
+ {
+ var longitude = double.Parse(cords[0], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture);
+ var latitude = double.Parse(cords[1], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture);
+ double? altitude = null;
+
+ if (cords.Length == 3) // the altitude is optional
+ {
+ altitude = double.Parse(cords[2], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture);
+ }
+
+ var point = new Point {Longitude = longitude, Latitude = latitude, Altitude = altitude};
+
+ // If point is the same as the previous point then skip it
+ if (points.Count > 0 && points[points.Count - 1].Equals(point))
+ continue;
+
+ points.Add(point);
+ }
+ catch (Exception exc)
+ {
+ throw new KMLException("Coordinate is in wrong format: " + tp, exc);
+ }
+ }
+
+ return points;
+ }
+
+ ///
+ /// This method performs the visit which is common to all geography instances.
+ /// It stores extracted information into the given geography object.
+ ///
+ /// Xml element to be visited
+ /// Geography object where data should be stored
+ protected static void VisitGeography(XmlElement elem, Geography geography)
+ {
+ #region The id in the KML string
+
+ geography.Id = GetAttribute(elem, "id");
+
+ #endregion
+
+ #region The extrude flag
+
+ var extrudeElem = GetFirstChild(elem, "extrude");
+ if (extrudeElem != null && extrudeElem.HasChildNodes)
+ {
+ var val = extrudeElem.ChildNodes[0].InnerText;
+ if (val.Trim().Equals("1"))
+ {
+ geography.Extrude = true;
+ }
+ }
+
+ #endregion
+
+ #region The tessellate flag
+
+ var tessellateElem = GetFirstChild(elem, "tessellate");
+ if (tessellateElem != null && tessellateElem.HasChildNodes)
+ {
+ var val = tessellateElem.ChildNodes[0].InnerText;
+ if (val.Trim().Equals("1"))
+ {
+ geography.Tessellate = true;
+ }
+ }
+
+ #endregion
+ }
+
+ ///
+ /// This method visits a KML Point element
+ ///
+ /// KML Point element to be visited
+ protected void VisitPoint(XmlElement elem)
+ {
+ if (elem == null) return;
+
+ // Extracts the point coordinates
+ var coordinates = GetFirstChild(elem, "coordinates");
+ if (coordinates == null || !coordinates.HasChildNodes)
+ return;
+
+ var coordinatesTextNode = coordinates.ChildNodes[0];
+
+ var coordinatesText = coordinatesTextNode.InnerText;
+
+ var pts = ParseCoordinates(coordinatesText);
+
+ if (pts.Count == 0)
+ return;
+
+ var point = pts[0];
+
+ // Extracts the altitude mode
+ point.Measure = GetAltitudeModeCode(elem);
+
+ // Extracts the other non-geographic data
+ VisitGeography(elem, point);
+
+ // Sets the geography instance context
+ point.Context = (elem.ParentNode != null) ? elem.ParentNode.OuterXml : elem.OuterXml;
+
+ Geographies.Add(point);
+ }
+
+ ///
+ /// This method visits a KML LineString element
+ ///
+ /// KML line string element to be visited
+ /// True if the line is the ring
+ protected void VisitLineString(
+ XmlElement elem,
+ bool isLineRing = false)
+ {
+ if (elem == null) return;
+
+ XmlNode coordinates = GetFirstChild(elem, "coordinates");
+ if (coordinates == null || !coordinates.HasChildNodes)
+ return;
+
+ var txt = coordinates.ChildNodes[0];
+
+ var allCoordinates = txt.InnerText;
+
+ var points = ParseCoordinates(allCoordinates);
+ if (points == null || points.Count == 0)
+ return;
+
+ var ls = isLineRing ? new LinearRing() : new LineString();
+
+ ls.Points.AddRange(points);
+
+ var altitudeModeCode = GetAltitudeModeCode(elem);
+
+ foreach (var p in ls.Points)
+ {
+ p.Measure = altitudeModeCode;
+ }
+
+ // Extract other non-geographic data
+ VisitGeography(elem, ls);
+
+ #region Handle tessellate flag
+
+ ls.StoreTessellateFlag();
+
+ #endregion
+
+ // Sets the geography instance context
+ ls.Context = (elem.ParentNode != null) ? elem.ParentNode.OuterXml : elem.OuterXml;
+
+ Geographies.Add(ls);
+ }
+
+ ///
+ /// This method visits a KML LinearRing element
+ ///
+ /// KML LinearRing element to be visited
+ protected void VisitLinearRing(XmlElement elem)
+ {
+ VisitLineString(elem, true);
+ }
+
+ ///
+ /// This method visits a KML Polygon element
+ ///
+ /// KML Polygon element to be visited
+ protected void VisitPolygon(XmlElement elem)
+ {
+ if (elem == null) return;
+
+ #region Extract Outer Border
+
+ var outerBoundaryIs = GetFirstChild(elem, "outerBoundaryIs");
+ if (outerBoundaryIs == null)
+ return;
+
+ var outerLinearRing = GetFirstChild(outerBoundaryIs, "LinearRing");
+ if (outerLinearRing == null)
+ return;
+
+ var numOfGeographiesBefore = Geographies.Count;
+
+ VisitLinearRing(outerLinearRing);
+
+ var numOfGeographiesAfter = Geographies.Count;
+
+ // Extracts the outer ring
+ if (numOfGeographiesAfter == numOfGeographiesBefore)
+ throw new KMLException("Outer ring not found!");
+
+ var index = Geographies.Count - 1;
+ var g = Geographies[index];
+ Geographies.RemoveAt(index);
+
+ if (!(g is LinearRing outerRing))
+ throw new KMLException("Outer ring not created!");
+
+ var polygon = new Polygon();
+
+ var isValidPolygon = true;
+
+ #region Check outer ring orientation. Outer boundary should be oriented counter-clockwise
+
+ if (!outerRing.IsValid)
+ {
+ // Checks if just the ring's orientation is wrong
+ outerRing.SwitchOrientation();
+
+ if (!outerRing.IsValid)
+ {
+ isValidPolygon = false;
+ outerRing.SwitchOrientation();
+ }
+ }
+
+ #endregion
+
+ polygon.OuterRing = outerRing;
+
+ #endregion
+
+ #region Extract Inner Borders
+
+ var innerBoundaryIsLst = elem.GetElementsByTagName("innerBoundaryIs");
+ foreach (XmlNode innerBorderNode in innerBoundaryIsLst)
+ {
+ if (!(innerBorderNode is XmlElement innerBoundaryIs))
+ continue;
+
+ var innerLinearRing = GetFirstChild(innerBoundaryIs, "LinearRing");
+ if (innerLinearRing == null)
+ continue;
+
+ numOfGeographiesBefore = Geographies.Count;
+
+ VisitLinearRing(innerLinearRing);
+
+ numOfGeographiesAfter = Geographies.Count;
+
+ // Extracts the inner ring
+ if (numOfGeographiesBefore == numOfGeographiesAfter)
+ throw new KMLException("Inner ring not found!");
+
+ index = Geographies.Count - 1;
+ g = Geographies[index];
+ Geographies.RemoveAt(index);
+
+ if (!(g is LinearRing innerRing))
+ throw new KMLException("Inner ring not created!");
+
+ #region Check the ring's orientation. Inner rings should be clockwise oriented
+
+ if (isValidPolygon)
+ {
+ if (innerRing.IsValid)
+ {
+ // The inner rings should be in the opposite direction from the outer ring
+
+ innerRing.SwitchOrientation();
+ }
+ }
+
+ #endregion
+
+ polygon.InnerRing.Add(innerRing);
+ }
+
+ #endregion
+
+ // Extracts the other non-geographic data
+ VisitGeography(elem, polygon);
+
+ #region Extract altitude mode
+
+ var altitudeModeCode = GetAltitudeModeCode(elem);
+ if (altitudeModeCode.HasValue)
+ {
+ // Updates all the points on the outer bound
+ if (polygon.OuterRing != null)
+ {
+ foreach (var p in polygon.OuterRing.Points)
+ {
+ p.Measure = altitudeModeCode;
+ }
+ }
+
+ // Update all the points on all the inner bounds
+ if (polygon.InnerRing != null)
+ {
+ foreach (var r in polygon.InnerRing)
+ {
+ if (r.Points == null)
+ return;
+
+ foreach (var p1 in r.Points)
+ {
+ p1.Measure = altitudeModeCode;
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region Handles tessellate flag
+
+ if (polygon.Tessellate)
+ {
+ polygon.OuterRing?.StoreTessellateFlag();
+
+ if (polygon.InnerRing != null)
+ {
+ foreach (var r in polygon.InnerRing)
+ r.StoreTessellateFlag();
+ }
+ }
+
+ #endregion
+
+ // Sets the geography instance context
+ polygon.Context = (elem.ParentNode != null) ? elem.ParentNode.OuterXml : elem.OuterXml;
+
+ Geographies.Add(polygon);
+ }
+
+ ///
+ /// This method visits a KML MultiGeometry element
+ ///
+ /// KML MultiGeometry element to be visited
+ protected void VisitMultiGeometry(XmlElement element)
+ {
+ if (element == null)
+ return;
+
+ var mg = new MultiGeometry();
+
+ if (element.HasChildNodes)
+ {
+ foreach (XmlNode child in element.ChildNodes)
+ {
+ var lastIndexBefore = Geographies.Count - 1;
+
+ Visit(child);
+
+ var lastIndex = Geographies.Count - 1;
+
+ if (lastIndex > lastIndexBefore)
+ {
+ var childGeography = Geographies[lastIndex];
+ Geographies.RemoveAt(lastIndex);
+ mg.Geographies.Add(childGeography);
+ }
+ }
+ }
+
+ // Sets the geography instance context
+ mg.Context = (element.ParentNode != null) ? element.ParentNode.OuterXml : element.OuterXml;
+
+ Geographies.Add(mg);
+ }
+
+ ///
+ /// This method visits a KML Region element
+ ///
+ /// KML Region element to be visited
+ protected void VisitRegion(XmlElement element)
+ {
+ if (element == null) return;
+
+ var r = new Region();
+
+ VisitGeography(element, r);
+
+ var latLonAltBoxElem = GetFirstChild(element, "LatLonAltBox");
+ var latLonBoxElem = GetFirstChild(element, "LatLonBox");
+ var latLonQuadElem = GetFirstChild(element, "gx:LatLonQuad");
+
+ var numOfGeographiesBefore = Geographies.Count;
+
+ if (latLonAltBoxElem != null)
+ {
+ VisitLatLonAltBox(latLonAltBoxElem);
+ }
+ else if (latLonBoxElem != null)
+ {
+ VisitLatLonBox(latLonBoxElem);
+ }
+ else if (latLonQuadElem != null)
+ {
+ VisitLatLonQuad(latLonQuadElem);
+ }
+
+ var numOfGeographiesAfter = Geographies.Count;
+
+ if (numOfGeographiesAfter > numOfGeographiesBefore)
+ {
+ // Found region's box
+
+ r.Box = Geographies[Geographies.Count - 1];
+ Geographies.RemoveAt(Geographies.Count - 1);
+ }
+
+ Regions.Add(r);
+ }
+
+ ///
+ /// This method visits a KML LatLonAltBox element
+ ///
+ /// KML LatLonAltBox element to be visited
+ protected void VisitLatLonAltBox(XmlElement element)
+ {
+ if (element == null) return;
+
+ #region Required properties
+
+ var north = GetChildElementText(element, "north");
+ var south = GetChildElementText(element, "south");
+ var west = GetChildElementText(element, "west");
+ var east = GetChildElementText(element, "east");
+
+ #endregion
+
+ #region Optional properties
+
+ double minAltitude = 0;
+ double maxAltitude = 0;
+ var minAltitudeS = GetChildElementText(element, "minAltitude");
+ if (!string.IsNullOrEmpty(minAltitudeS))
+ {
+ minAltitude = double.Parse(minAltitudeS);
+ }
+
+ var maxAltitudeS = GetChildElementText(element, "maxAltitude");
+ if (!string.IsNullOrEmpty(maxAltitudeS))
+ {
+ maxAltitude = double.Parse(maxAltitudeS);
+ }
+
+ var altitudeMode = GetAltitudeMode(element);
+
+ #endregion
+
+ var altBox = new LatLonAltBox
+ {
+ North = north,
+ South = south,
+ West = west,
+ East = east,
+ MinAltitude = minAltitude,
+ MaxAltitude = maxAltitude,
+ AltitudeMode = altitudeMode
+ };
+
+ Geographies.Add(altBox);
+ }
+
+ ///
+ /// This method visits a KML LatLonBox element
+ ///
+ /// KML LatLonBox element to be visited
+ protected void VisitLatLonBox(XmlElement element)
+ {
+ if (element == null) return;
+
+ #region Required properties
+
+ var north = GetChildElementText(element, "north");
+ var south = GetChildElementText(element, "south");
+ var west = GetChildElementText(element, "west");
+ var east = GetChildElementText(element, "east");
+
+ #endregion
+
+ #region Optional properties
+
+ double rotation = 0;
+
+ var rotationS = GetChildElementText(element, "rotation");
+ if (!string.IsNullOrEmpty(rotationS))
+ {
+ rotation = double.Parse(rotationS);
+ }
+
+ #endregion
+
+ var altBox = new LatLonBox
+ {
+ North = north,
+ South = south,
+ West = west,
+ East = east,
+ Rotation = rotation
+ };
+
+ Geographies.Add(altBox);
+ }
+
+ ///
+ /// This method visits a KML LatLonQuad element
+ ///
+ /// LatLonQuad KML element to be visited
+ protected void VisitLatLonQuad(XmlElement element)
+ {
+ if (element == null) return;
+
+ var coordinates = GetChildElementText(element, "coordinates");
+
+ var points = ParseCoordinates(coordinates);
+ if (points == null || points.Count != 4)
+ throw new KMLException("In KML element LatLonQuad has to have exactly the 4 coordinates.");
+
+ // Closes the polygon. Stores the last point the same as the first one.
+ points.Add(points[0].Clone());
+
+ var llq = new LatLonQuad();
+
+ llq.Points.Clear();
+ llq.Points.AddRange(points);
+
+ Geographies.Add(llq);
+ }
+
+ ///
+ /// This method visits a KML element. Method extracts the child elements:
+ /// Longitude, Latitude, Altitude and AltitudeMode and creates the point instance from those values.
+ ///
+ /// KML element to be visited
+ protected void VisitLonLatAltElement(XmlElement element)
+ {
+ double longitude = 0, latitude = 0, altitude = 0;
+ int? altitudeMode;
+ try
+ {
+ var longitudeElem = GetFirstChild(element, "longitude");
+ if (longitudeElem != null && longitudeElem.HasChildNodes)
+ {
+ var s = longitudeElem.ChildNodes[0].InnerText;
+ longitude = double.Parse(s);
+ }
+ var latitudeElem = GetFirstChild(element, "latitude");
+ if (latitudeElem != null && latitudeElem.HasChildNodes)
+ {
+ var s = latitudeElem.ChildNodes[0].InnerText;
+ latitude = double.Parse(s);
+ }
+ var altitudeElem = GetFirstChild(element, "altitude");
+ if (altitudeElem != null && altitudeElem.HasChildNodes)
+ {
+ var s = altitudeElem.ChildNodes[0].InnerText;
+ altitude = double.Parse(s);
+ }
+
+ altitudeMode = GetAltitudeModeCode(element);
+ }
+ catch (Exception exc)
+ {
+ throw new KMLException("Location coordinates in wrong format!", exc);
+ }
+
+ var pt = new Point(longitude, latitude, altitude, altitudeMode);
+ Geographies.Add(pt);
+ }
+
+ ///
+ /// This method visits a KML Model element
+ ///
+ /// KML model element to be visited
+ protected void VisitModel(XmlElement element)
+ {
+ if (element == null) return;
+
+ var m = new Model();
+
+ #region Extract Location
+
+ var locationElem = GetFirstChild(element, "location");
+ if (locationElem != null)
+ {
+ VisitLonLatAltElement(locationElem);
+
+ m.Location = Geographies[Geographies.Count - 1] as Point;
+ Geographies.RemoveAt(Geographies.Count - 1);
+ }
+
+ #endregion
+
+ #region Extract other non-geographic data
+
+ VisitGeography(element, m);
+
+ var orientationElem = GetFirstChild(element, "orientation");
+ if (orientationElem != null)
+ {
+ m.OrientationHeading = GetChildElementText(orientationElem, "heading", 0);
+ m.OrientationTilt = GetChildElementText(orientationElem, "tilt", 0);
+ m.OrientationRoll = GetChildElementText(orientationElem, "roll", 0);
+ }
+
+ var scaleElem = GetFirstChild(element, "scale");
+ if (scaleElem != null)
+ {
+ m.ScaleX = GetChildElementText(scaleElem, "x", 1);
+ m.ScaleY = GetChildElementText(scaleElem, "y", 1);
+ m.ScaleZ = GetChildElementText(scaleElem, "z", 1);
+ }
+
+ #endregion
+
+ Models.Add(m);
+ }
+
+ #endregion
+
+ #region Public methods
+
+ ///
+ /// This method parses the given KML string
+ ///
+ public void Parse()
+ {
+ // Visits the all child nodes
+ foreach (XmlNode n in Document.ChildNodes)
+ {
+ Visit(n);
+ }
+ }
+
+ #endregion
+
+ #region Public Static Methods
+
+ ///
+ /// This method parses the given XML file context
+ ///
+ /// File full path
+ /// KMLParser with loaded file context
+ public static KMLParser ParseFile(string fileFullPathName)
+ {
+ var text = System.IO.File.ReadAllText(fileFullPathName);
+
+ var p = new KMLParser(text);
+
+ return p;
+ }
+
+ ///
+ /// This method returns the first child element with the given name in the given parent element
+ ///
+ /// Parent element
+ /// Child element name
+ /// Child element if it exists, null otherwise
+ public static XmlElement GetFirstChild(XmlElement element, string childElementName)
+ {
+ if (element == null || element.HasChildNodes == false || string.IsNullOrEmpty(childElementName))
+ return null;
+
+ childElementName = childElementName.ToLowerInvariant();
+
+ foreach (XmlNode node in element.ChildNodes)
+ {
+ var c = node as XmlElement;
+ if (c == null)
+ continue;
+
+ if (c.Name.ToLowerInvariant().Equals(childElementName))
+ return c;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This method determines the altitude mode for the given KML geography element.
+ ///
+ /// KML geography element whose altitude mode should be found
+ /// Altitude mode, or null if the altitude mode is not found
+ private static AltitudeMode? GetAltitudeMode(XmlElement elem)
+ {
+ var altitudeModeVal = "clamptoground";
+ var altitudeMode = GetFirstChild(elem, "altitudeMode") ?? GetFirstChild(elem, "gx:altitudeMode");
+
+ if (altitudeMode == null)
+ return null;
+
+ if (altitudeMode.HasChildNodes && altitudeMode.ChildNodes[0].Value != null)
+ {
+ altitudeModeVal = altitudeMode.ChildNodes[0].Value.Trim().ToLowerInvariant();
+ }
+
+ if (altitudeModeVal.Equals("clamptoground"))
+ return AltitudeMode.ClampToGround;
+ if (altitudeModeVal.Equals("relativetoground"))
+ return AltitudeMode.RelativeToGround;
+ if (altitudeModeVal.Equals("absolute"))
+ return AltitudeMode.Absolute;
+ if (altitudeModeVal.Equals("relativetoseafloor"))
+ return AltitudeMode.RelativeToSeaFloor;
+ if (altitudeModeVal.Equals("clamptoseafloor"))
+ return AltitudeMode.ClampToSeaFloor;
+
+ throw new KMLException("Altitude mode not supported: " + altitudeModeVal);
+ }
+
+ ///
+ /// This method determines the altitude mode code for the given KML element.
+ ///
+ /// KML element
+ /// Altitude mode code, or null if the altitude mode is not found
+ public static int? GetAltitudeModeCode(XmlElement elem)
+ {
+ var altitudeMode = GetAltitudeMode(elem);
+
+ if (altitudeMode == null)
+ return null;
+
+ return (int)altitudeMode;
+ }
+
+ ///
+ /// This method returns the tessellate flag value for the given KML element.
+ ///
+ /// KML element
+ /// Tessellate flag value
+ public static bool GetTessellateFlag(XmlElement elem)
+ {
+ var tessellateFlag = false;
+ XmlNode tessellate = GetFirstChild(elem, "tessellate");
+ if (tessellate != null && tessellate.HasChildNodes)
+ {
+ var tessTxt = tessellate.ChildNodes[0];
+ if (tessTxt.InnerText.Trim().Equals("1"))
+ tessellateFlag = true;
+ }
+
+ return tessellateFlag;
+ }
+
+ ///
+ /// This method returns the attribute value of the given xml element.
+ ///
+ /// Xml element whose attribute is requested
+ /// Attribute name
+ /// Attribute value, or null value if the attribute is not set
+ public static string GetAttribute(XmlElement elem, string attributeName)
+ {
+ var attribute = elem.Attributes[attributeName];
+
+ return attribute?.Value;
+ }
+
+ ///
+ /// This method returns the inner text in the child element of the given parent element
+ ///
+ /// Parent element
+ /// Child element name
+ /// Child element inner text
+ public static string GetChildElementText(XmlElement element, string childElementName)
+ {
+ if (element == null || !element.HasChildNodes) return null;
+
+ var child = GetFirstChild(element, childElementName);
+
+ return child?.InnerText;
+ }
+
+ ///
+ /// This method returns the inner text in the child element of the given parent element.
+ /// The inner text value is converted to the given type T
+ ///
+ /// Type to convert the inner text value into
+ /// Parent element
+ /// Child element name
+ /// Child text value converted into the type T
+ public static T GetChildElementText(XmlElement element, string childElementName)
+ {
+ var childInnerText = GetChildElementText(element, childElementName);
+
+ if (string.IsNullOrEmpty(childInnerText))
+ return default(T);
+
+ return (T)Convert.ChangeType(childInnerText, typeof(T));
+ }
+
+ ///
+ /// This method returns the inner text in the child element of the given parent element.
+ /// The inner text value is converted to the given type T. If the child with the given name doesn't exist then
+ /// the given default value will be returned.
+ ///
+ /// Type to convert the inner text value into
+ /// Parent element
+ /// Child element name
+ /// In case that the child doesn't exist, this value will be returned
+ /// Child text value converted into the type T, or the default value if the child is not found
+ public static T GetChildElementText(XmlElement element, string childElementName, T defaultValue)
+ {
+ var childInnerText = GetChildElementText(element, childElementName);
+
+ if (string.IsNullOrEmpty(childInnerText))
+ return defaultValue;
+
+ return (T)Convert.ChangeType(childInnerText, typeof(T));
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/KMLProcessor.cs b/src/KMLProcessor/Import/KMLProcessor.cs
new file mode 100644
index 0000000..ab11657
--- /dev/null
+++ b/src/KMLProcessor/Import/KMLProcessor.cs
@@ -0,0 +1,122 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+using Microsoft.SqlServer.Types;
+// ReSharper disable NotAccessedField.Local
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class processes the given KML string and produces the equivalent SqlGeography instance
+ /// using sinks.
+ ///
+ public class KMLProcessor
+ {
+ #region Constructors
+
+ ///
+ /// Constructor. Accepts the KML string.
+ ///
+ /// KML data to be parsed
+ public KMLProcessor(string kml)
+ {
+ _kml = kml;
+ _parser = new KMLParser(kml);
+ _parser.Parse();
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Spatial reference identifier (SRID).
+ ///
+ // ReSharper disable MemberCanBePrivate.Global
+ public int Srid { get; set; } = Constants.DefaultSRID;
+
+ ///
+ /// Placemarks extracted from the given KML string
+ ///
+ public IList Placemarks => _parser.Placemarks;
+
+ ///
+ /// Models extracted from the given KML string
+ ///
+ public IList Models => _parser.Models;
+
+ ///
+ /// Regions extracted from the given KML string
+ ///
+ public IList Regions => _parser.Regions;
+
+ ///
+ /// Geography instances extracted from the given KML string
+ ///
+ public IEnumerable Geographies => _parser.Geographies;
+
+ ///
+ /// Ground overlays extracted from the given KML string
+ ///
+ public IList GroundOverlays => _parser.GroundOverlays;
+
+ #endregion
+
+ #region Private Data
+
+ ///
+ /// KML/XML parser
+ ///
+ private readonly KMLParser _parser;
+
+ ///
+ /// KML string to be parsed
+ ///
+ private readonly string _kml;
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// This method populates the given sink with the information about the geography instance
+ /// extracted from the KML string
+ ///
+ /// Sink to be filled
+ /// If true and the extracted geography instance is invalid then the MakeValid
+ /// function will be executed on the extracted geography instance
+ public void Populate(
+ IGeographySink110 sink,
+ bool makeValid)
+ {
+ sink.SetSrid(Srid);
+
+ var numOfGeographies = _parser.Geographies.Count;
+ if (numOfGeographies == 1)
+ {
+ _parser.Geographies[0].Populate(sink, makeValid);
+ }
+ else if (numOfGeographies > 1)
+ {
+ sink.BeginGeography(OpenGisGeographyType.GeometryCollection);
+
+ foreach (var g in _parser.Geographies)
+ {
+ g.Populate(sink, makeValid);
+ }
+
+ sink.EndGeography();
+ }
+ else
+ {
+ // Geography instance is not found. The empty geography collection will be generated.
+ sink.BeginGeography(OpenGisGeographyType.GeometryCollection);
+ sink.EndGeography();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/LatLonAltBox.cs b/src/KMLProcessor/Import/LatLonAltBox.cs
new file mode 100644
index 0000000..50de90a
--- /dev/null
+++ b/src/KMLProcessor/Import/LatLonAltBox.cs
@@ -0,0 +1,84 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class contains the information about the LatLonAltBox element extracted from KML
+ ///
+ public class LatLonAltBox : LatLonBoxBase
+ {
+ #region Public Data
+
+ ///
+ /// The minimal altitude
+ ///
+ public double MinAltitude
+ {
+ get => _minAltitude;
+ set
+ {
+ if (value < 0)
+ throw new KMLException("Altitude has to be greater than 0");
+
+ _minAltitude = value;
+
+ if (_maxAltitude < _minAltitude)
+ _maxAltitude = _minAltitude;
+ }
+ }
+ ///
+ /// Data member for the MinAltitude property
+ ///
+ private double _minAltitude;
+
+ ///
+ /// The maximal altitude
+ ///
+ public double MaxAltitude
+ {
+ get => _maxAltitude;
+ set
+ {
+ if (value < 0)
+ throw new KMLException("Altitude has to be greater than 0");
+
+ _maxAltitude = value;
+ if (_minAltitude > _maxAltitude)
+ _minAltitude = _maxAltitude;
+ }
+ }
+ ///
+ /// Data member for the property MaxAltitude
+ ///
+ private double _maxAltitude;
+
+ ///
+ /// Altitude mode
+ ///
+ public AltitudeMode? AltitudeMode { get; set; }
+
+ #endregion
+
+ #region Geography Methods
+
+ ///
+ /// This method populates the given sink with the data from this geography instance
+ ///
+ /// Sink to be populated
+ public override void Populate(Microsoft.SqlServer.Types.IGeographySink110 sink)
+ {
+ // Initializes the altitude to the maximal value
+ _maxAltitude = MaxAltitude;
+
+ // Converts and stores the altitude mode to the spatial m coordinate
+ if (AltitudeMode != null)
+ Measure = (int)AltitudeMode.Value;
+
+ base.Populate(sink);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/LatLonBox.cs b/src/KMLProcessor/Import/LatLonBox.cs
new file mode 100644
index 0000000..e66879d
--- /dev/null
+++ b/src/KMLProcessor/Import/LatLonBox.cs
@@ -0,0 +1,21 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class holds the information about the LatLonBox instance extracted from the KML file
+ ///
+ public class LatLonBox : LatLonBoxBase
+ {
+ #region Public Data
+
+ ///
+ /// The rotation angle.
+ ///
+ public double Rotation { get; set; }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/LatLonBoxBase.cs b/src/KMLProcessor/Import/LatLonBoxBase.cs
new file mode 100644
index 0000000..c1179a1
--- /dev/null
+++ b/src/KMLProcessor/Import/LatLonBoxBase.cs
@@ -0,0 +1,154 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// Base class for the all KML boxes. All KML boxes will be converted to polygon instances.
+ ///
+ public class LatLonBoxBase : Geography
+ {
+ #region Public data
+
+ ///
+ /// The north side latitude
+ ///
+ public double North { private get; set; }
+
+ ///
+ /// The south side latitude
+ ///
+ public double South { private get; set; }
+
+ ///
+ /// The east side longitude
+ ///
+ public double East { private get; set; }
+
+ ///
+ /// The west side longitude
+ ///
+ public double West { private get; set; }
+
+ // ReSharper disable UnusedAutoPropertyAccessor.Global
+ ///
+ /// Altitude
+ ///
+ public double? Altitude { private get; set; }
+
+ ///
+ /// Measure
+ ///
+ public double? Measure { private get; set; }
+ // ReSharper restore UnusedAutoPropertyAccessor.Global
+
+ ///
+ /// SqlGeography instance well-known text.
+ ///
+ public override string WKT
+ {
+ get
+ {
+ CheckCoordinates();
+
+ var wkt = "polygon((";
+
+ // (west, south)
+ wkt += new Point(West, South, Altitude, Measure).Coordinate;
+
+ // (east, south)
+ wkt += "," + new Point(East, South, Altitude, Measure).Coordinate;
+
+ // (east, north)
+ wkt += "," + new Point(East, North, Altitude, Measure).Coordinate;
+
+ // (west, north)
+ wkt += "," + new Point(West, North, Altitude, Measure).Coordinate;
+
+ // (west, south)
+ wkt += "," + new Point(West, South, Altitude, Measure).Coordinate;
+
+ wkt += "))";
+ return wkt;
+ }
+ }
+
+ #endregion
+
+ #region Geography Methods
+
+ ///
+ /// This method populates the given sink with the data from this geography instance
+ ///
+ /// Sink to be populated
+ public override void Populate(IGeographySink110 sink)
+ {
+ CheckCoordinates();
+
+ // The coordinates for this geography instance will be:
+ // (west, south), (east, south), (east, north), (west, north), (west, south)
+ sink.BeginGeography(OpenGisGeographyType.Polygon);
+
+ // (west, south)
+ sink.BeginFigure(South, West, Altitude, Measure);
+
+ // (east, south)
+ sink.AddLine(South, East, Altitude, Measure);
+
+ // (east, north)
+ sink.AddLine(North, East, Altitude, Measure);
+
+ // (west, north)
+ sink.AddLine(North, West, Altitude, Measure);
+
+ // (west, south)
+ sink.AddLine(South, West, Altitude, Measure);
+
+ sink.EndFigure();
+
+ sink.EndGeography();
+ }
+
+ #endregion
+
+ #region Protected Methods
+
+ ///
+ /// This method checks if the south latitude is less then the north latitude.
+ /// If not then the south and the north will be swapped.
+ /// This method also checks if the west longitude if less then the east longitude.
+ /// If not then the east and the west will be swapped.
+ ///
+ protected void CheckCoordinates()
+ {
+ // Checks if the South and the North latitude are in the range (-90, 90)
+ South = Utilities.ShiftInRange(South, 90);
+ North = Utilities.ShiftInRange(North, 90);
+
+ // Checks if the West and the East longitude are in the range (-180, 180)
+ West = Utilities.ShiftInRange(West, 180);
+ East = Utilities.ShiftInRange(East, 180);
+
+ // Ensures that the south is less then the north
+ if (South > North)
+ {
+ var tmp = South;
+ South = North;
+ North = tmp;
+ }
+
+ // Ensures that the west is less then the east
+ if (West > East)
+ {
+ var tmp = West;
+ West = East;
+ East = tmp;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/LatLonQuad.cs b/src/KMLProcessor/Import/LatLonQuad.cs
new file mode 100644
index 0000000..f4f7fca
--- /dev/null
+++ b/src/KMLProcessor/Import/LatLonQuad.cs
@@ -0,0 +1,79 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class holds the information about a LatLonQuad instance extracted from the KML file
+ ///
+ public class LatLonQuad : Geography
+ {
+ #region Public Data
+
+ ///
+ /// The quad vertices
+ ///
+ public List Points { get; } = new List();
+
+ ///
+ /// SqlGeography instance well-known text.
+ ///
+ public override string WKT
+ {
+ get
+ {
+ if (Points == null || Points.Count == 0)
+ throw new KMLException("WKT is not defined.");
+
+ var wkt = "polygon((";
+
+ var first = true;
+ foreach (var p in Points)
+ {
+ if (first)
+ first = false;
+ else
+ wkt += ", ";
+
+ wkt += p.WKT;
+ }
+
+ wkt += "))";
+ return wkt;
+ }
+ }
+
+ #endregion
+
+ #region Geography methods
+
+ ///
+ /// This method populates the given sink with the data from this geography instance
+ ///
+ /// Sink to be populated
+ public override void Populate(IGeographySink110 sink)
+ {
+ if (Points == null || Points.Count == 0)
+ return;
+
+ sink.BeginGeography(OpenGisGeographyType.Polygon);
+
+ sink.BeginFigure(Points[0].Latitude, Points[0].Longitude, Points[0].Altitude, Points[0].Measure);
+
+ for (var i = 1; i < Points.Count; i++)
+ {
+ sink.AddLine(Points[i].Latitude, Points[i].Longitude, Points[i].Altitude, Points[i].Measure);
+ }
+
+ sink.EndFigure();
+
+ sink.EndGeography();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/LineString.cs b/src/KMLProcessor/Import/LineString.cs
new file mode 100644
index 0000000..8b115bd
--- /dev/null
+++ b/src/KMLProcessor/Import/LineString.cs
@@ -0,0 +1,116 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class contains the information about a line string extracted from the KML file
+ ///
+ public class LineString : Geography
+ {
+ #region Private Fields
+
+ ///
+ /// OpenGisGeographyType for this geography instance
+ ///
+ protected OpenGisGeographyType GeographyType = OpenGisGeographyType.LineString;
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// The list of points
+ ///
+ public List Points { get; } = new List();
+
+ ///
+ /// SqlGeography instance well-known text.
+ ///
+ public override string WKT => "LINESTRING" + Vertices;
+
+ ///
+ /// Returns the vertices of this linear ring. The returned string will be in the following format:
+ /// (V0, V1, V2,..., Vn, V0). Vi will be in the format: longitude latitude [altitude [measure]]
+ ///
+ public string Vertices
+ {
+ get
+ {
+ var result = "(";
+ var isFirst = true;
+ foreach (var p in Points)
+ {
+ if (!isFirst)
+ result += ", ";
+ else
+ isFirst = false;
+
+ result += p.Longitude + " " + p.Latitude;
+ if (p.Altitude.HasValue)
+ {
+ result += " " + p.Altitude.Value;
+
+ if (p.Measure.HasValue)
+ result += " " + p.Measure.Value;
+ }
+ }
+ result += ")";
+
+ return result;
+ }
+ }
+
+ #endregion
+
+ #region Geography Methods
+
+ ///
+ /// This method populates the given sink with the data from this geography instance
+ ///
+ /// Sink to be populated
+ public override void Populate(IGeographySink110 sink)
+ {
+ if (Points == null || Points.Count == 0)
+ return;
+
+ sink.BeginGeography(GeographyType);
+ sink.BeginFigure(Points[0].Latitude, Points[0].Longitude, Points[0].Altitude, Points[0].Measure);
+
+ for (var i = 1; i < Points.Count; i++)
+ {
+ sink.AddLine(Points[i].Latitude, Points[i].Longitude, Points[i].Altitude, Points[i].Measure);
+ }
+
+ sink.EndFigure();
+ sink.EndGeography();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// This method stores the tessellate flag. M coordinates of all the point will be set
+ /// to the "clamp to ground" value.
+ ///
+ public void StoreTessellateFlag()
+ {
+ if (!Tessellate) return;
+
+ foreach (var p in Points)
+ {
+ if (!p.Measure.HasValue)
+ {
+ p.Measure = (int)AltitudeMode.ClampToGround;
+ }
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/LinearRing.cs b/src/KMLProcessor/Import/LinearRing.cs
new file mode 100644
index 0000000..25bd92b
--- /dev/null
+++ b/src/KMLProcessor/Import/LinearRing.cs
@@ -0,0 +1,57 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class contains the data about a linear ring extracted from the KML file
+ ///
+ public class LinearRing : LineString
+ {
+ #region Constructors
+
+ ///
+ /// Default constructor
+ ///
+ public LinearRing()
+ {
+ GeographyType = OpenGisGeographyType.Polygon;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// This method switches the ring's orientation
+ ///
+ public void SwitchOrientation()
+ {
+ var i = 1;
+ var j = Points.Count - 2; // index of the element before last
+ while (i < j)
+ {
+ var tmp = Points[i];
+ Points[i] = Points[j];
+ Points[j] = tmp;
+
+ i++;
+ j--;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// SqlGeography instance well-known text.
+ ///
+ public override string WKT => "POLYGON(" + Vertices + ")";
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/Model.cs b/src/KMLProcessor/Import/Model.cs
new file mode 100644
index 0000000..7ba299d
--- /dev/null
+++ b/src/KMLProcessor/Import/Model.cs
@@ -0,0 +1,79 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class holds the information about a KML Model element
+ ///
+ public class Model : Geography
+ {
+ #region Public Data
+
+ ///
+ /// The location of the model
+ ///
+ public Point Location { private get; set; }
+
+ ///
+ /// The orientation heading
+ ///
+ public double OrientationHeading { get; set; }
+
+ ///
+ /// The orientation tilt
+ ///
+ public double OrientationTilt { get; set; }
+
+ ///
+ /// The orientation roll
+ ///
+ public double OrientationRoll { get; set; }
+
+ ///
+ /// The x-axis scale factor
+ ///
+ public double ScaleX { get; set; }
+
+ ///
+ /// The y-axis scale factor
+ ///
+ public double ScaleY { get; set; }
+
+ ///
+ /// The z-axis scale factor
+ ///
+ public double ScaleZ { get; set; }
+
+ ///
+ /// SqlGeography instance well-known text.
+ ///
+ public override string WKT
+ {
+ get
+ {
+ if (Location != null)
+ return Location.WKT;
+
+ throw new KMLException("WKT is not defined.");
+ }
+ }
+
+ #endregion
+
+ #region Geography Methods
+
+ ///
+ /// This method populates the given sink with the data from this geography instance
+ ///
+ /// Sink to be populated
+ public override void Populate(Microsoft.SqlServer.Types.IGeographySink110 sink)
+ {
+ Location?.Populate(sink);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/MultiGeometry.cs b/src/KMLProcessor/Import/MultiGeometry.cs
new file mode 100644
index 0000000..96d3411
--- /dev/null
+++ b/src/KMLProcessor/Import/MultiGeometry.cs
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class holds the information about a collection of the KML geographies
+ ///
+ public class MultiGeometry : Geography
+ {
+ #region Public Properties
+
+ ///
+ /// Geographies contained in this geography instance
+ ///
+ public IList Geographies { get; set; } = new List();
+
+ ///
+ /// SqlGeography instance well-known text.
+ ///
+ public override string WKT
+ {
+ get
+ {
+ var wkt = "GEOMETRYCOLLECTION(";
+
+ var firstGeography = true;
+ foreach (var g in Geographies)
+ {
+ if (firstGeography)
+ firstGeography = false;
+ else
+ wkt += ", ";
+
+ wkt += g.WKT;
+ }
+
+ wkt += ")";
+ return wkt;
+ }
+ }
+
+ #endregion
+
+ #region Geography Methods
+
+ ///
+ /// This method populates the given sink with the information about this multi geometry instance
+ ///
+ /// Sink to be populated
+ public override void Populate(IGeographySink110 sink)
+ {
+ sink.BeginGeography(OpenGisGeographyType.GeometryCollection);
+
+ if (Geographies != null && Geographies.Count > 0)
+ {
+ foreach (var geography in Geographies)
+ {
+ geography.Populate(sink);
+ }
+ }
+
+ sink.EndGeography();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/Placemark.cs b/src/KMLProcessor/Import/Placemark.cs
new file mode 100644
index 0000000..52970bd
--- /dev/null
+++ b/src/KMLProcessor/Import/Placemark.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class contains the information about a placemark extracted from the KML file
+ ///
+ public class Placemark
+ {
+ #region Public Properties
+
+ ///
+ /// Identifier
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// Name
+ ///
+ public string Name
+ {
+ get => _mName;
+ set => _mName = string.IsNullOrEmpty(value) ? "" : value;
+ }
+ ///
+ /// Data member for the property Name
+ ///
+ private string _mName = "";
+
+ ///
+ /// Description
+ ///
+ public string Description
+ {
+ get => _mDescription;
+ set => _mDescription = string.IsNullOrEmpty(value) ? "" : value;
+ }
+ ///
+ /// Data member for the Description property
+ ///
+ private string _mDescription = "";
+
+ ///
+ /// Address
+ ///
+ public string Address
+ {
+ get => _mAddress;
+ set => _mAddress = string.IsNullOrEmpty(value) ? "" : value;
+ }
+ ///
+ /// Data member for the Address property
+ ///
+ private string _mAddress = "";
+
+ ///
+ /// The geography instance which describes this placemark
+ ///
+ public Geography Geography { get; set; }
+
+ ///
+ /// The geography instance where this placemark looks at
+ ///
+ public Geography LookAt { get; set; }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/Point.cs b/src/KMLProcessor/Import/Point.cs
new file mode 100644
index 0000000..642130e
--- /dev/null
+++ b/src/KMLProcessor/Import/Point.cs
@@ -0,0 +1,163 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+#pragma warning disable CS0659
+ // Type overrides Object.Equals(object o) but does not override Object.GetHashCode()
+ ///
+ /// This class holds the information about a Point extracted from the KML file
+ ///
+ public class Point : Geography
+#pragma warning restore CS0659
+ {
+ #region Constructors
+
+ ///
+ /// Default constructor
+ ///
+ public Point() : this(0, 0)
+ {
+ }
+
+ ///
+ /// Constructor. Constructs the point using the given longitude, latitude, altitude and measure.
+ ///
+ /// Longitude
+ /// Latitude
+ /// Altitude
+ /// Measure
+ public Point(double longitude, double latitude, double? altitude = null, double? measure = null)
+ {
+ Longitude = longitude;
+ Latitude = latitude;
+ Altitude = altitude;
+ Measure = measure;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Longitude.
+ ///
+ public double Longitude { get; set; }
+
+ ///
+ /// Latitude.
+ ///
+ public double Latitude { get; set; }
+
+ ///
+ /// Altitude.
+ ///
+ public double? Altitude { get; set; }
+
+ ///
+ /// Measure
+ ///
+ public double? Measure { get; set; }
+
+ ///
+ /// SqlGeography instance well-known text.
+ ///
+ public override string WKT
+ {
+ get
+ {
+ var wkt = $"POINT({Coordinate})";
+
+ return wkt;
+ }
+ }
+
+ ///
+ /// Point's coordinate in the format: longitude, latitude [, altitude]
+ ///
+ public string Coordinate
+ {
+ get
+ {
+ var wkt = $"{Longitude} {Latitude}";
+
+ if (Altitude.HasValue)
+ wkt += " " + Altitude.Value;
+
+ if (Measure.HasValue)
+ wkt += " " + Measure.Value;
+
+ return wkt;
+ }
+ }
+
+ #endregion
+
+ #region Geography methods
+
+ ///
+ /// This method populates the given sink with the data about this geography instance
+ ///
+ /// Sink to be populated
+ public override void Populate(IGeographySink110 sink)
+ {
+ sink.BeginGeography(OpenGisGeographyType.Point);
+ sink.BeginFigure(Latitude, Longitude, Altitude, Measure);
+
+ sink.EndFigure();
+ sink.EndGeography();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// This method clones this point
+ ///
+ /// Clone of this point
+ public Point Clone()
+ {
+ return new Point
+ {
+ Longitude = Longitude,
+ Latitude = Latitude,
+ Altitude = Altitude,
+ Measure = Measure
+ };
+ }
+
+ #endregion
+
+ #region Overridden Base Methods
+
+ ///
+ /// This method compares the given object with this point
+ ///
+ /// Object to be compared with this point
+ ///
+#pragma warning disable 659
+ public override bool Equals(object obj)
+#pragma warning restore 659
+ {
+ if (this == obj)
+ return true;
+
+ if (!(obj is Point rhs))
+ return false;
+
+ return Longitude.EqualsTo(rhs.Longitude) &&
+ Latitude.EqualsTo(rhs.Latitude) &&
+ // ReSharper disable PossibleInvalidOperationException
+ (Altitude == null && rhs.Altitude == null || Altitude.Value.EqualsTo(rhs.Altitude.Value)) &&
+ (Measure == null && rhs.Measure == null || (Measure.Value.EqualsTo(rhs.Measure.Value)));
+ // ReSharper restore PossibleInvalidOperationException
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/Polygon.cs b/src/KMLProcessor/Import/Polygon.cs
new file mode 100644
index 0000000..ce44db9
--- /dev/null
+++ b/src/KMLProcessor/Import/Polygon.cs
@@ -0,0 +1,119 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class contains the information about a polygon extracted from the KML file
+ ///
+ public class Polygon : Geography
+ {
+ #region Public Properties
+
+ ///
+ /// Outer border
+ ///
+ public LinearRing OuterRing { get; set; }
+
+ ///
+ /// Inner borders/rings
+ ///
+ public IList InnerRing { get; } = new List();
+
+ ///
+ /// SqlGeography instance well-known text.
+ ///
+ public override string WKT
+ {
+ get
+ {
+ var wkt = "POLYGON(";
+
+ if (OuterRing != null)
+ wkt += OuterRing.Vertices;
+ else
+ throw new Exception("Outer ring is not set.");
+
+ foreach (var linearRing in InnerRing)
+ {
+ wkt += ", " + linearRing.Vertices;
+ }
+
+ wkt += ")";
+
+ return wkt;
+ }
+ }
+
+ #endregion
+
+ #region Geography Methods
+
+ ///
+ /// This method populates the given sink with the data from this geography instance
+ ///
+ /// Sink to be populated
+ public override void Populate(IGeographySink110 sink)
+ {
+ if (OuterRing?.Points == null || OuterRing.Points.Count == 0)
+ return;
+
+ sink.BeginGeography(OpenGisGeographyType.Polygon);
+
+ // Populates the outer boundary
+ sink.BeginFigure(
+ OuterRing.Points[0].Latitude,
+ OuterRing.Points[0].Longitude,
+ OuterRing.Points[0].Altitude,
+ OuterRing.Points[0].Measure);
+
+ for (var i = 1; i < OuterRing.Points.Count; i++)
+ {
+ sink.AddLine(
+ OuterRing.Points[i].Latitude,
+ OuterRing.Points[i].Longitude,
+ OuterRing.Points[i].Altitude,
+ OuterRing.Points[i].Measure);
+ }
+
+ sink.EndFigure();
+
+ if (InnerRing != null && InnerRing.Count > 0)
+ {
+ // Populates the inner boundaries
+
+ foreach (var linearRing in InnerRing)
+ {
+ if (linearRing.Points == null || linearRing.Points.Count == 0)
+ continue;
+
+ sink.BeginFigure(
+ linearRing.Points[0].Latitude,
+ linearRing.Points[0].Longitude,
+ linearRing.Points[0].Altitude,
+ linearRing.Points[0].Measure);
+
+ for (var i = 1; i < linearRing.Points.Count; i++)
+ {
+ sink.AddLine(
+ linearRing.Points[i].Latitude,
+ linearRing.Points[i].Longitude,
+ linearRing.Points[i].Altitude,
+ linearRing.Points[i].Measure);
+ }
+
+ sink.EndFigure();
+ }
+ }
+
+ sink.EndGeography();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/Import/Region.cs b/src/KMLProcessor/Import/Region.cs
new file mode 100644
index 0000000..8c9b936
--- /dev/null
+++ b/src/KMLProcessor/Import/Region.cs
@@ -0,0 +1,48 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+namespace SQLSpatialTools.KMLProcessor.Import
+{
+ ///
+ /// This class holds the information about a Region extracted from the KML file
+ ///
+ public class Region : Geography
+ {
+ #region Public Data
+
+ ///
+ /// Box which defines the border of this region
+ ///
+ public Geography Box { private get; set; }
+
+ ///
+ /// SqlGeography instance well-known text.
+ ///
+ public override string WKT
+ {
+ get
+ {
+ if (Box != null)
+ return Box.WKT;
+
+ throw new KMLException("WKT is not defined.");
+ }
+ }
+
+ #endregion
+
+ #region Geography Methods
+
+ ///
+ /// This method populates the given sink with the data from this geography instance
+ ///
+ /// Sink to be populated
+ public override void Populate(Microsoft.SqlServer.Types.IGeographySink110 sink)
+ {
+ Box?.Populate(sink);
+ }
+
+ #endregion
+ }
+}
diff --git a/KMLProcessor/KMLException.cs b/src/KMLProcessor/KMLException.cs
similarity index 69%
rename from KMLProcessor/KMLException.cs
rename to src/KMLProcessor/KMLException.cs
index 6dbfcd2..6d308b5 100644
--- a/KMLProcessor/KMLException.cs
+++ b/src/KMLProcessor/KMLException.cs
@@ -1,36 +1,37 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace Microsoft.SqlServer.SpatialToolbox.KMLProcessor
-{
- ///
- /// Instance of this class will be thrown for every exception which occures in the KML processor.
- ///
- public class KMLException : Exception
- {
- #region Constructors
-
- ///
- /// Default constructor
- ///
- /// Exception message
- public KMLException(string message)
- : base(message)
- {
- }
-
- ///
- /// Constructor. Constructs the KMLException using the given exception message and the inner exception
- ///
- /// Exception message
- /// Inner exception
- public KMLException(string message, Exception innerException)
- : base(message, innerException)
- {
- }
-
- #endregion
- }
-}
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+
+namespace SQLSpatialTools.KMLProcessor
+{
+ ///
+ /// Instance of this class will be thrown for every exception which occurs in the KML processor.
+ ///
+ public class KMLException : Exception
+ {
+ #region Constructors
+
+ ///
+ /// Default constructor
+ ///
+ /// Exception message
+ public KMLException(string message)
+ : base(message)
+ {
+ }
+
+ ///
+ /// Constructor. Constructs the KMLException using the given exception message and the inner exception
+ ///
+ /// Exception message
+ /// Inner exception
+ public KMLException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ #endregion
+ }
+}
diff --git a/src/KMLProcessor/KmlFunctions.cs b/src/KMLProcessor/KmlFunctions.cs
new file mode 100644
index 0000000..e1f4d55
--- /dev/null
+++ b/src/KMLProcessor/KmlFunctions.cs
@@ -0,0 +1,287 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections;
+using System.Collections.ObjectModel;
+using System.Data;
+using System.Data.SqlClient;
+using System.Data.SqlTypes;
+using System.IO;
+using System.Xml;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.KMLProcessor.Export;
+using SQLSpatialTools.KMLProcessor.Import;
+// ReSharper disable UnusedMember.Global
+// ReSharper disable MemberCanBePrivate.Global
+
+namespace SQLSpatialTools.KMLProcessor
+{
+ ///
+ /// This class contains the KML import/export functions
+ ///
+ public class KMLFunctions
+ {
+ #region Public Methods
+
+ #region Import Methods
+
+ ///
+ /// This function parses the given KML string and returns the extracted geography instance.
+ ///
+ /// KML string to be parsed
+ /// If true and the extracted geography instance is invalid then the MakeValid
+ /// function will be executed on the extracted geography instance
+ /// Extracted geography instance
+ [Microsoft.SqlServer.Server.SqlFunction]
+ public static SqlGeography KmlToGeography(
+ SqlString kml,
+ SqlBoolean makeValid)
+ {
+ if (kml == null || string.IsNullOrEmpty(kml.Value))
+ {
+ return SqlGeography.Null;
+ }
+
+ var constructed = new SqlGeographyBuilder();
+ var parser = new Import.KMLProcessor(kml.Value.Trim());
+ parser.Populate(constructed, makeValid.Value);
+ return constructed.ConstructedGeography;
+ }
+
+ #endregion
+
+ #region TVF Import Functions
+
+ ///
+ /// This TVF function parses the given KML string. All geography instances will be extracted and
+ /// returned along with the context where they were found.
+ /// One record will be returned for each extracted geography instance.
+ ///
+ /// The return table format is: (Id NVARCHAR(200), Context NVARCHAR(MAX), Shape Geography)
+ ///
+ /// KML string which should be processed
+ /// If true and extracted geography instance is invalid then the MakeValid
+ /// function will be executed on that geography instance
+ [Microsoft.SqlServer.Server.SqlFunction(
+ FillRowMethodName = "DecodeGeographyContext",
+ TableDefinition = "Id NVARCHAR(200), Context NVARCHAR(MAX), Shape Geography"
+ )]
+ public static IEnumerable ImportKml(
+ SqlString kml,
+ SqlBoolean makeValid)
+ {
+ if (kml == null || string.IsNullOrEmpty(kml.Value))
+ return new Collection();
+
+ var geographies = new Collection();
+
+ var parser =
+ new Import.KMLProcessor(kml.Value.Trim());
+
+ foreach (var p in parser.Geographies)
+ {
+ var ge = new GeographyContext
+ {
+ Id = p.Id, Context = p.Context, Shape = p.ToSqlGeography(makeValid.Value)
+ };
+
+ geographies.Add(ge);
+ }
+
+ return geographies;
+ }
+
+ ///
+ /// This method is a helper method for the TVF function ImportKml. A Sql Server uses it to decode a row
+ /// returned by the ImportKml function.
+ ///
+ /// A row returned by the ImportKml function
+ /// Id extracted from a rowData
+ /// Context extracted from a rowData
+ /// Shape extracted from a rowData
+ public static void DecodeGeographyContext(
+ object rowData,
+ out SqlString id,
+ out SqlString context,
+ out SqlGeography shape)
+ {
+ id = new SqlString();
+ context = new SqlString();
+ shape = null;
+
+ if (!(rowData is GeographyContext data)) return;
+
+ id = data.Id;
+ context = data.Context;
+ shape = data.Shape;
+
+ }
+
+ #endregion
+
+ #region Export Methods
+
+ ///
+ /// This method converts the given geography instance into the KML format
+ ///
+ /// Geography instance to be converted
+ /// KML representation of the given geography instance
+ [Microsoft.SqlServer.Server.SqlFunction]
+ public static SqlString ExportToKml(SqlGeography g)
+ {
+ if (g == null || g.IsNull)
+ return new SqlString("");
+
+ var stream = new MemoryStream();
+ var writer = XmlWriter.Create(stream);
+
+ var sink = new KeyholeMarkupLanguageGeography(writer);
+
+ g.Populate(sink);
+
+ sink.FinalizeKMLDocument();
+
+ writer.Flush();
+ stream.Seek(0, SeekOrigin.Begin);
+ var ba = stream.ToArray();
+ var sData = System.Text.Encoding.UTF8.GetChars(ba);
+ var str = new string(sData);
+ writer.Close();
+
+ // Removes everything before
+ var xmlIndex = str.IndexOf("", StringComparison.Ordinal);
+ if (xmlIndex > 0)
+ str = str.Remove(0, xmlIndex);
+
+ return new SqlString(str);
+ }
+
+ ///
+ /// This method converts the given geography instance represented as a WKT string, into the string in the KML format.
+ ///
+ /// Geography instance represented as the WKT string
+ /// The KML string
+ [Microsoft.SqlServer.Server.SqlFunction]
+ public static SqlString ExportWKTToKml(SqlString wkt)
+ {
+ if (wkt == null || string.IsNullOrEmpty(wkt.Value))
+ return new SqlString("");
+
+ var g = SqlGeography.Parse(wkt);
+ return ExportToKml(g);
+ }
+
+ #endregion
+
+ #endregion
+
+ #region Protected Methods
+
+ ///
+ /// This method stores the given box into the database
+ ///
+ /// Box to be saved
+ /// The KmlId to connect this object to
+ /// Sql connection
+ /// Sql transaction
+ /// The database id which is assigned to the given box
+ protected static int? SaveLatLonBox(Geography box, int insertedKmlId, SqlConnection con, SqlTransaction tran)
+ {
+ #region Insert Geography
+
+ int? insertedGeographyId = null;
+
+ if (box != null)
+ {
+ var saveGeographyCmd =
+ new SqlCommand(
+ " insert into Geographies(Geography, KmlId) " +
+ " values(geography::Parse('" + box + "'), @KmlId);select scope_identity()", con, tran);
+
+ AddParameter(saveGeographyCmd, "KmlId", insertedKmlId, DbType.Int32);
+
+ insertedGeographyId = (int)((decimal)saveGeographyCmd.ExecuteScalar());
+ box.DbId = insertedGeographyId;
+ }
+
+ #endregion
+
+ #region Insert Lat Lon Box
+
+ var saveLatLonBox =
+ new SqlCommand(
+ " insert into LatLonBoxes(BoxType, GeographyId, rotation, MinAlt, MaxAlt, AltitudeMode) " +
+ " values(@BoxType, @GeographyId, @rotation, @MinAlt, @MaxAlt, @AltitudeMode);select scope_identity()",
+ con, tran);
+
+ AddParameter(saveLatLonBox, "GeographyId", insertedGeographyId, DbType.Int32);
+
+ var boxType = "";
+
+ if (box is LatLonBox latLonBox)
+ {
+ boxType = "LatLonBox";
+ AddParameter(saveLatLonBox, "rotation", latLonBox.Rotation, DbType.Double);
+ }
+ else
+ {
+ AddParameter(saveLatLonBox, "rotation", null, DbType.Double);
+ }
+
+ if (box is LatLonAltBox latLonAltBox)
+ {
+ boxType = "LatLonAltBox";
+
+ AddParameter(saveLatLonBox, "MinAlt", latLonAltBox.MinAltitude, DbType.Double);
+ AddParameter(saveLatLonBox, "MaxAlt", latLonAltBox.MaxAltitude, DbType.Double);
+ AddParameter(saveLatLonBox, "AltitudeMode", latLonAltBox.AltitudeMode.ToString(), DbType.String);
+ }
+ else
+ {
+ AddParameter(saveLatLonBox, "MinAlt", null, DbType.Double);
+ AddParameter(saveLatLonBox, "MaxAlt", null, DbType.Double);
+ AddParameter(saveLatLonBox, "AltitudeMode", null, DbType.Double);
+ }
+
+ // ReSharper disable once UnusedVariable
+ if (box is LatLonQuad latLonQuad)
+ {
+ boxType = "LatLonQuad";
+ }
+
+ AddParameter(saveLatLonBox, "BoxType", boxType, DbType.String);
+
+ int? insertedLatLonBox = (int)((decimal)saveLatLonBox.ExecuteScalar());
+
+ #endregion
+
+ return insertedLatLonBox;
+ }
+
+ ///
+ /// This method adds a new parameter to the given sql command
+ ///
+ /// Command to add parameter to
+ /// Parameter name
+ /// Parameter value
+ /// Parameter database type
+ protected static void AddParameter(SqlCommand cmd, string parameterName, object value, DbType type)
+ {
+ if (value == null)
+ value = DBNull.Value;
+
+ cmd.Parameters.Add(new SqlParameter()
+ {
+ ParameterName = parameterName,
+ Value = value,
+ DbType = type
+ });
+ }
+
+ #endregion
+ }
+}
+
+
diff --git a/src/KMLProcessor/Utilities.cs b/src/KMLProcessor/Utilities.cs
new file mode 100644
index 0000000..d6ed348
--- /dev/null
+++ b/src/KMLProcessor/Utilities.cs
@@ -0,0 +1,44 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.KMLProcessor
+{
+ ///
+ /// This class contains the utility methods
+ ///
+ public static class Utilities
+ {
+ ///
+ /// This method shifts the given value into the range [-upperBorder, upperBorder]
+ ///
+ /// The value to be shifted
+ /// The range's upper bound
+ /// The shifted value
+ public static double ShiftInRange(double value, double upperBorder)
+ {
+ upperBorder = Math.Abs(upperBorder);
+
+ if (upperBorder.EqualsTo(0))
+ throw new KMLException("The range has to be greater then 0.");
+
+ var rangeSize = 2 * upperBorder;
+
+ if (value > upperBorder)
+ {
+ var k = (int)((value + upperBorder) / rangeSize);
+ value -= k * rangeSize;
+ }
+ else if (value < -1 * upperBorder)
+ {
+ var k = (int)((value - upperBorder) / rangeSize);
+ value -= k * rangeSize;
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/Projections/AlbersEqualAreaProjection.cs b/src/Projections/AlbersEqualAreaProjection.cs
new file mode 100644
index 0000000..f25363b
--- /dev/null
+++ b/src/Projections/AlbersEqualAreaProjection.cs
@@ -0,0 +1,85 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// References: http://mathworld.wolfram.com/AlbersEqual-AreaConicProjection.html
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+
+namespace SQLSpatialTools.Projections
+{
+ // EPSG Code: 9822
+ // OGC WKT Name: Albers_Conic_Equal_Area
+ internal sealed class AlbersEqualAreaProjection : Projection
+ {
+ // longitude0, latitude0, parallel1, parallel2
+ public AlbersEqualAreaProjection(IDictionary parameters)
+ : base(parameters)
+ {
+ var latitude0Rad = InputLatitude("latitude0");
+ var parallel1Rad = InputLatitude("parallel1");
+ var parallel2Rad = InputLatitude("parallel2");
+
+ var cosParallel1 = Math.Cos(parallel1Rad);
+ var sinParallel1 = Math.Sin(parallel1Rad);
+
+ _n2 = sinParallel1 + Math.Sin(parallel2Rad);
+ if (Math.Abs(_n2) <= MathX.Tolerance)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ _n = _n2 / 2;
+ _nHalf = _n2 / 4;
+ var invN2 = 1 / _n2;
+ _invN = 2 / _n2;
+
+ _c = MathX.Square(cosParallel1) + sinParallel1 * _n2;
+ _cOverN2 = _c * invN2;
+
+ var a = _c - Math.Sin(latitude0Rad) * _n2;
+ if (a < 0)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ _ro0 = Math.Sqrt(a) * _invN;
+ }
+
+ protected internal override void Project(double latitude, double longitude, out double x, out double y)
+ {
+ var a = _c - Math.Sin(latitude) * _n2;
+ if (a < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(latitude));
+ }
+
+ var ro = Math.Sqrt(a) * _invN;
+
+ var theta = longitude * _n;
+ x = ro * Math.Sin(theta);
+ y = _ro0 - ro * Math.Cos(theta);
+ }
+
+ protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
+ {
+ var ros = x * x + (_ro0 - y) * (_ro0 - y);
+ var a = _cOverN2 - ros * _nHalf;
+ if (double.IsNaN(a) || Math.Abs(a) > 1 + MathX.Tolerance)
+ {
+ throw new ArgumentOutOfRangeException(paramName:string.Join(",", new []{nameof(x),nameof(y)}));
+ }
+
+ latitude = Math.Asin(MathX.Clamp(1, a));
+ longitude = MathX.Clamp(Math.PI, MathX.Atan2(x, _ro0 - y, "x, y") * _invN);
+ }
+
+ private readonly double _c;
+ private readonly double _cOverN2;
+ private readonly double _ro0;
+ private readonly double _n2;
+ private readonly double _n;
+ private readonly double _nHalf;
+ private readonly double _invN;
+ }
+}
\ No newline at end of file
diff --git a/Projections/EquirectangularProjection.cs b/src/Projections/EquirectangularProjection.cs
similarity index 80%
rename from Projections/EquirectangularProjection.cs
rename to src/Projections/EquirectangularProjection.cs
index 54a3fd1..988968b 100644
--- a/Projections/EquirectangularProjection.cs
+++ b/src/Projections/EquirectangularProjection.cs
@@ -1,46 +1,47 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//
-// References: http://en.wikipedia.org/wiki/Equirectangular_projection
-//------------------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-
-namespace SQLSpatialTools
-{
- internal sealed class EquirectangularProjection : Projection
- {
- // Angles are in degrees.
- // longitude0:
- // parallel: standard parallels (north and south of the equator) where the scale of the projection is true
- //
- public EquirectangularProjection(Dictionary parameters)
- : base(parameters)
- {
- double parallel_rad = InputLatitude("parallel");
-
- _scale = Math.Cos(parallel_rad);
-
- // scale > 0 because |parallel| <= 89.9
- Debug.Assert(_scale > 0, _scale.ToString(CultureInfo.InvariantCulture));
- _scaleInv = 1 / _scale;
- }
-
- protected internal override void Project(double latitude, double longitude, out double x, out double y)
- {
- x = longitude * _scale;
- y = latitude;
- }
-
- protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
- {
- longitude = x * _scaleInv;
- latitude = y;
- }
-
- private readonly double _scale;
- private readonly double _scaleInv; // = 1 / scale
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// References: http://en.wikipedia.org/wiki/Equirectangular_projection
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace SQLSpatialTools.Projections
+{
+ internal sealed class EquirectangularProjection : Projection
+ {
+ // Angles are in degrees.
+ // longitude0:
+ // parallel: standard parallels (north and south of the equator) where the scale of the projection is true
+ //
+ public EquirectangularProjection(IDictionary parameters)
+ : base(parameters)
+ {
+ var parallelRadian = InputLatitude("parallel");
+
+ _scale = Math.Cos(parallelRadian);
+
+ // scale > 0 because |parallel| <= 89.9
+ Debug.Assert(_scale > 0, _scale.ToString(CultureInfo.InvariantCulture));
+ _scaleInv = 1 / _scale;
+ }
+
+ protected internal override void Project(double latitude, double longitude, out double x, out double y)
+ {
+ x = longitude * _scale;
+ y = latitude;
+ }
+
+ protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
+ {
+ longitude = x * _scaleInv;
+ latitude = y;
+ }
+
+ private readonly double _scale;
+ private readonly double _scaleInv; // = 1 / scale
+ }
}
\ No newline at end of file
diff --git a/Projections/GnomonicProjection.cs b/src/Projections/GnomonicProjection.cs
similarity index 62%
rename from Projections/GnomonicProjection.cs
rename to src/Projections/GnomonicProjection.cs
index ef08834..eeb5235 100644
--- a/Projections/GnomonicProjection.cs
+++ b/src/Projections/GnomonicProjection.cs
@@ -1,87 +1,87 @@
-//------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation.
-//
-// References: http://mathworld.wolfram.com/GnomonicProjection.html
-//
-// Note: The gnomonic projction is the only projection that maps SqlGeography
-// Polygons and LineString exactly to their SqlGeometry counterparts.
-//
-//------------------------------------------------------------------------------
-using System;
-using System.Diagnostics;
-using System.Globalization;
-using System.Collections.Generic;
-
-namespace SQLSpatialTools
-{
- // EPSG Code: None
- // OGC WKT Name: Gnomonic
- internal sealed class GnommonicProjection : Projection
- {
- // Input argument: the center of the projection
- public GnommonicProjection(Dictionary parameters)
- : base(parameters)
- {
- _center = Util.SphericalRadToCartesian(InputLatitude("latitude1", 90), InputLongitude("longitude1", 360));
-
- // This projection is designed for numerical computations rather than cartography.
- // The choice of coordinate basis for the tangent plane - which affects the
- // orientation of the projection in the xy plane - is optimized for accuracy rather
- // than good looks. The first basis vector is obtained by dropping the smallest coordinate,
- // switching the other two, and flipping the sign of one of them. The second one is
- // obtained by cross product.
-
- double[] center = { _center.x, _center.y, _center.z };
- double[] vector = new double[3];
-
- int k = GetMinEntry(center);
- int j = (k + 2) % 3;
- int i = (j + 2) % 3;
-
- vector[i] = -center[j];
- vector[j] = center[i];
- vector[k] = 0;
-
- _xAxis = new Vector3(vector[0], vector[1], vector[2]).Unitize();
-
- _yAxis = _center.CrossProduct(_xAxis);
- }
-
- private static int GetMinEntry(double[] values)
- {
- int i = 0;
- if (Math.Abs(values[1]) < Math.Abs(values[0]))
- i = 1;
- if (Math.Abs(values[2]) < Math.Abs(values[i]))
- i = 2;
- return i;
- }
-
- protected internal override void Project(double latitude, double longitude, out double x, out double y)
- {
- Vector3 vector = Util.SphericalRadToCartesian(latitude, longitude);
- double r = vector * _center;
-
- if (r < _tolerance)
- {
- throw new ArgumentOutOfRangeException("Input point is too far away from the center of projection.");
- }
- vector = vector / r;
-
- x = vector * _xAxis;
- y = vector * _yAxis;
- }
-
- protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
- {
- Vector3 vector = _center + _xAxis * x + _yAxis * y;
- latitude = Util.Latitude(vector);
- longitude = Util.Longitude(vector);
- }
-
- private readonly Vector3 _center;
- private readonly Vector3 _xAxis;
- private readonly Vector3 _yAxis;
- private static readonly double _tolerance = 1e-8;
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// References: http://mathworld.wolfram.com/GnomonicProjection.html
+//
+// Note: The gnomonic projection is the only projection that maps SqlGeography
+// Polygons and LineString exactly to their SqlGeometry counterparts.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using SQLSpatialTools.Types;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Projections
+{
+ // EPSG Code: None
+ // OGC WKT Name: Gnomonic
+ internal sealed class GnomonicProjection : Projection
+ {
+ // Input argument: the center of the projection
+ public GnomonicProjection(Dictionary parameters)
+ : base(parameters)
+ {
+ _center = SpatialUtil.SphericalRadToCartesian(InputLatitude("latitude1", 90), InputLongitude("longitude1", 360));
+
+ // This projection is designed for numerical computations rather than cartography.
+ // The choice of coordinate basis for the tangent plane - which affects the
+ // orientation of the projection in the xy plane - is optimized for accuracy rather
+ // than good looks. The first basis vector is obtained by dropping the smallest coordinate,
+ // switching the other two, and flipping the sign of one of them. The second one is
+ // obtained by cross product.
+
+ double[] center = { _center.X, _center.Y, _center.Z };
+ var vector = new double[3];
+
+ var k = GetMinEntry(center);
+ var j = (k + 2) % 3;
+ var i = (j + 2) % 3;
+
+ vector[i] = -center[j];
+ vector[j] = center[i];
+ vector[k] = 0;
+
+ _xAxis = new Vector3(vector[0], vector[1], vector[2]).Unitize();
+
+ _yAxis = _center.CrossProduct(_xAxis);
+ }
+
+ private static int GetMinEntry(IList values)
+ {
+ var i = 0;
+ if (Math.Abs(values[1]) < Math.Abs(values[0]))
+ i = 1;
+ if (Math.Abs(values[2]) < Math.Abs(values[i]))
+ i = 2;
+ return i;
+ }
+
+ protected internal override void Project(double latitude, double longitude, out double x, out double y)
+ {
+ var vector = SpatialUtil.SphericalRadToCartesian(latitude, longitude);
+ var r = vector * _center;
+
+ if (r < _tolerance)
+ {
+ throw new ArgumentOutOfRangeException(nameof(latitude), "Input point is too far away from the center of projection.");
+ }
+ vector = vector / r;
+
+ x = vector * _xAxis;
+ y = vector * _yAxis;
+ }
+
+ protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
+ {
+ var vector = _center + _xAxis * x + _yAxis * y;
+ latitude = SpatialUtil.Latitude(vector);
+ longitude = SpatialUtil.Longitude(vector);
+ }
+
+ private readonly Vector3 _center;
+ private readonly Vector3 _xAxis;
+ private readonly Vector3 _yAxis;
+ private static readonly double _tolerance = 1e-8;
+ }
}
\ No newline at end of file
diff --git a/src/Projections/LambertConformalConicProjection.cs b/src/Projections/LambertConformalConicProjection.cs
new file mode 100644
index 0000000..ef1c7e6
--- /dev/null
+++ b/src/Projections/LambertConformalConicProjection.cs
@@ -0,0 +1,78 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// References: http://mathworld.wolfram.com/LambertConformalConicProjection.html
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace SQLSpatialTools.Projections
+{
+ internal sealed class LambertConformalConicProjection : Projection
+ {
+ // Angles are in degrees.
+ // longitude0:
+ // latitude: latitude for the origin of Cartesian coordinates
+ // fi1: standard parallel
+ // fi2: standard parallel
+ //
+ public LambertConformalConicProjection(IDictionary parameters)
+ : base(parameters)
+ {
+ var latitudeRadian = InputLatitude("latitude0");
+ var fi1Radian = InputLatitude("fi1");
+ var fi2Radian = InputLatitude("fi2");
+
+ if (Math.Abs(fi1Radian - fi2Radian) <= MathX.Tolerance)
+ {
+ throw new ArgumentException(Resource.Fi1AndFi2MustBeDifferent);
+ }
+
+ // a != 0 and b != 0 because fi1 != fi2
+ var a = Math.Log(Math.Cos(fi1Radian) / Math.Cos(fi2Radian));
+ var b = Math.Log(Math.Tan(Math.PI / 4 + fi2Radian / 2) / Math.Tan(Math.PI / 4 + fi1Radian / 2));
+ Debug.Assert(!double.IsNaN(a) && !double.IsInfinity(a) && Math.Abs(a) > 0, a.ToString(CultureInfo.InvariantCulture));
+ Debug.Assert(!double.IsNaN(b) && !double.IsInfinity(b) && Math.Abs(b) > 0, b.ToString(CultureInfo.InvariantCulture));
+
+ _n = a / b;
+ _nInv = b / a;
+
+ // tan_fi1 > 0 because fi1 is in range (-Pi/2, Pi/2)
+ var tanFi1 = Math.Tan(Math.PI / 4 + fi1Radian / 2);
+ Debug.Assert(!double.IsInfinity(tanFi1) && tanFi1 > 0, tanFi1.ToString(CultureInfo.InvariantCulture));
+ _f = Math.Cos(fi1Radian) * Math.Pow(tanFi1, _n) * _nInv;
+ Debug.Assert(Math.Abs(_f) > MathX.Tolerance);
+
+ // tan_latitude > 0 because latitude is in range (-Pi/2, Pi/2)
+ var tanLatitude = Math.Tan(Math.PI / 4 + latitudeRadian / 2);
+ Debug.Assert(!double.IsInfinity(tanLatitude) && tanLatitude > 0, tanLatitude.ToString(CultureInfo.InvariantCulture));
+ _ro0 = _f * Math.Pow(tanLatitude, -_n);
+ }
+
+ protected internal override void Project(double latitude, double longitude, out double x, out double y)
+ {
+ var a = Math.Tan(Math.PI / 4 + latitude / 2);
+ var ro = _f * (a <= MathX.Tolerance ? 0 : Math.Pow(a, -_n));
+ var theta = _n * longitude;
+
+ x = ro * Math.Sin(theta);
+ y = _ro0 - ro * Math.Cos(theta);
+ }
+
+ protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
+ {
+ var ro = Math.Sign(_n) * Math.Sqrt(x * x + MathX.Square(_ro0 - y));
+ // If ro is zero or very small then latitude will be +-Pi/2 depending on the sign of f.
+ latitude = 2 * Math.Atan(Math.Pow(_f / ro, _nInv)) - Math.PI / 2;
+ longitude = MathX.Clamp(Math.PI, Math.Atan2(x, _ro0 - y) * _nInv);
+ }
+
+ private readonly double _n;
+ private readonly double _nInv; // = 1 / n
+ private readonly double _f;
+ private readonly double _ro0;
+ }
+}
\ No newline at end of file
diff --git a/Projections/MathX.cs b/src/Projections/MathX.cs
similarity index 70%
rename from Projections/MathX.cs
rename to src/Projections/MathX.cs
index 276bf75..7e987ae 100644
--- a/Projections/MathX.cs
+++ b/src/Projections/MathX.cs
@@ -1,92 +1,94 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//
-// Purpose: Misc math functions.
-//------------------------------------------------------------------------------
-using System;
-using System.Globalization;
-
-namespace SQLSpatialTools
-{
- internal static class MathX
- {
- public static readonly double Tolerance = 1e-14;
-
- public static double Atan2(double y, double x, string name)
- {
- if (Math.Abs(y) <= Tolerance && Math.Abs(x) <= Tolerance)
- {
- throw new ArgumentException(name);
- }
- return Atan2(y, x);
- }
-
- public static double Atan2(double y, double x)
- {
- double a = Math.Atan2(y, x);
- return a == Math.PI ? -Math.PI : a;
- }
-
- public static double Clamp(double limit, double a)
- {
- if (a > limit)
- return limit;
- if (a < -limit)
- return -limit;
- return a;
- }
-
- public static double InputLat(double latDeg, double max, string name)
- {
- if (Double.IsNaN(latDeg) || latDeg < -max || latDeg > max)
- {
- throw new ArgumentOutOfRangeException(name, String.Format(CultureInfo.InvariantCulture, Resource.InputLatitudeIsOutOfRange, latDeg, max));
- }
- return Clamp(Math.PI / 2, Util.ToRadians(latDeg));
- }
-
- public static double InputLong(double longDeg, double max, string name)
- {
- if (Double.IsNaN(longDeg) || longDeg < -max || longDeg > max)
- {
- throw new ArgumentOutOfRangeException("longitude", String.Format(CultureInfo.InvariantCulture, Resource.InputLongitudeIsOutOfRange, longDeg, max));
- }
- return NormalizeLongitudeRad(Util.ToRadians(longDeg));
- }
-
- // Remainder(2, 1.5) == 0.5
- // Remainder(2, -1.5) == 0.5
- // Remainder(-2, 1.5) == 1
- // Remainder(-2, -1.5) == 1
- // def: x = y * integer + rem, 0 <= rem < x
- public static double Remainder(double x, double y)
- {
- return x - Math.Floor(x / y) * y;
- }
-
- // Returns longitude in range [-180, 180).
- // Has the same meaning as:
- // while(longitude >= 180) longitude -= 360
- // while(longitude < -180) longitude += 360
- //
- public static double NormalizeLongitudeDeg(double longitude)
- {
- return -180 <= longitude && longitude < 180 ? longitude : Remainder(longitude + 180, 360) - 180;
- }
-
- // Returns longitude in range [-Pi, Pi).
- // Has the same meaning as:
- // while(longitude >= Pi) longitude -= Pi * 2
- // while(longitude < -Pi) longitude += Pi * 2
- //
- public static double NormalizeLongitudeRad(double longitude)
- {
- return -Math.PI <= longitude && longitude < Math.PI ? longitude : Remainder(longitude + Math.PI, Math.PI * 2) - Math.PI;
- }
-
- public static double Square(double a)
- {
- return a * a;
- }
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Purpose: Misc math functions.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Globalization;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Projections
+{
+ internal static class MathX
+ {
+ public const double Tolerance = 1e-14;
+
+ public static double Atan2(double y, double x, string name)
+ {
+ if (Math.Abs(y) <= Tolerance && Math.Abs(x) <= Tolerance)
+ {
+ throw new ArgumentException(name);
+ }
+ return Atan2(y, x);
+ }
+
+ public static double Atan2(double y, double x)
+ {
+ var a = Math.Atan2(y, x);
+ return a.EqualsTo(Math.PI) ? - Math.PI : a;
+ }
+
+ public static double Clamp(double limit, double a)
+ {
+ if (a > limit)
+ return limit;
+ if (a < -limit)
+ return -limit;
+ return a;
+ }
+
+ public static double InputLat(double latDeg, double max, string name)
+ {
+ if (double.IsNaN(latDeg) || latDeg < -max || latDeg > max)
+ {
+ throw new ArgumentOutOfRangeException(name, string.Format(CultureInfo.InvariantCulture, Resource.InputLatitudeIsOutOfRange, latDeg, max));
+ }
+ return Clamp(Math.PI / 2, SpatialUtil.ToRadians(latDeg));
+ }
+
+ public static double InputLong(double longDeg, double max, string name)
+ {
+ if (double.IsNaN(longDeg) || longDeg < -max || longDeg > max)
+ {
+ throw new ArgumentOutOfRangeException(nameof(longDeg), string.Format(CultureInfo.InvariantCulture, Resource.InputLongitudeIsOutOfRange, longDeg, max));
+ }
+ return NormalizeLongitudeRad(SpatialUtil.ToRadians(longDeg));
+ }
+
+ // Remainder(2, 1.5) == 0.5
+ // Remainder(2, -1.5) == 0.5
+ // Remainder(-2, 1.5) == 1
+ // Remainder(-2, -1.5) == 1
+ // def: x = y * integer + rem, 0 <= rem < x
+ public static double Remainder(double x, double y)
+ {
+ return x - Math.Floor(x / y) * y;
+ }
+
+ // Returns longitude in range [-180, 180).
+ // Has the same meaning as:
+ // while(longitude >= 180) longitude -= 360
+ // while(longitude < -180) longitude += 360
+ //
+ public static double NormalizeLongitudeDeg(double longitude)
+ {
+ return -180 <= longitude && longitude < 180 ? longitude : Remainder(longitude + 180, 360) - 180;
+ }
+
+ // Returns longitude in range [-Pi, Pi).
+ // Has the same meaning as:
+ // while(longitude >= Pi) longitude -= Pi * 2
+ // while(longitude < -Pi) longitude += Pi * 2
+ //
+ public static double NormalizeLongitudeRad(double longitude)
+ {
+ return -Math.PI <= longitude && longitude < Math.PI ? longitude : Remainder(longitude + Math.PI, Math.PI * 2) - Math.PI;
+ }
+
+ public static double Square(double a)
+ {
+ return a * a;
+ }
+ }
}
\ No newline at end of file
diff --git a/Projections/MercatorProjection.cs b/src/Projections/MercatorProjection.cs
similarity index 65%
rename from Projections/MercatorProjection.cs
rename to src/Projections/MercatorProjection.cs
index f052b1f..cadddb2 100644
--- a/Projections/MercatorProjection.cs
+++ b/src/Projections/MercatorProjection.cs
@@ -1,47 +1,49 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//
-// References: http://mathworld.wolfram.com/MercatorProjection.html
-//------------------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-
-namespace SQLSpatialTools
-{
- internal sealed class MercatorProjection : Projection
- {
- private const double MaxLatitudeDeg = 89.5;
- private readonly double MaxY = Math.Log(Math.Tan(Math.PI / 4 + Util.ToRadians(MaxLatitudeDeg) / 2));
-
- // longitude0: Reference longitude
- //
- public MercatorProjection(Dictionary parameters)
- : base(parameters)
- {
- }
-
- protected internal override void Project(double latitude, double longitude, out double x, out double y)
- {
- x = longitude;
- y = MathX.Clamp(MaxY, Math.Log(Math.Tan(Math.PI / 4 + latitude / 2))); // protect againt +-Infinity
- }
-
- protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
- {
- longitude = x;
-
- if (y >= MaxY)
- {
- latitude = Math.PI / 2;
- }
- else if (y <= -MaxY)
- {
- latitude = -Math.PI / 2;
- }
- else
- {
- latitude = Math.Atan(Math.Exp(y)) * 2 - Math.PI / 2;
- }
- }
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// References: http://mathworld.wolfram.com/MercatorProjection.html
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Projections
+{
+ internal sealed class MercatorProjection : Projection
+ {
+ private const double MaxLatitudeDeg = 89.5;
+ private readonly double _maxY = Math.Log(Math.Tan(Math.PI / 4 + SpatialUtil.ToRadians(MaxLatitudeDeg) / 2));
+
+ // longitude0: Reference longitude
+ //
+ public MercatorProjection(IDictionary parameters)
+ : base(parameters)
+ {
+ }
+
+ protected internal override void Project(double latitude, double longitude, out double x, out double y)
+ {
+ x = longitude;
+ y = MathX.Clamp(_maxY, Math.Log(Math.Tan(Math.PI / 4 + latitude / 2))); // protect against +-Infinity
+ }
+
+ protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
+ {
+ longitude = x;
+
+ if (y >= _maxY)
+ {
+ latitude = Math.PI / 2;
+ }
+ else if (y <= -_maxY)
+ {
+ latitude = -Math.PI / 2;
+ }
+ else
+ {
+ latitude = Math.Atan(Math.Exp(y)) * 2 - Math.PI / 2;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Projections/ObliqueMercatorProjection.cs b/src/Projections/ObliqueMercatorProjection.cs
new file mode 100644
index 0000000..38bd9ac
--- /dev/null
+++ b/src/Projections/ObliqueMercatorProjection.cs
@@ -0,0 +1,89 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// References: http://mathworld.wolfram.com/MercatorProjection.html
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+
+namespace SQLSpatialTools.Projections
+{
+ internal sealed class ObliqueMercatorProjection : Projection
+ {
+ private const double MaxY = 5;
+
+ // Angles are in degrees.
+ // longitude0:
+ // fi1:
+ // lambda1:
+ // fi2:
+ // lambda2:
+ //
+ public ObliqueMercatorProjection(IDictionary parameters)
+ : base(parameters)
+ {
+ var fi1Rad = InputLatitude("fi1");
+ var fi2Rad = InputLatitude("fi2");
+
+ var lambda1Rad = InputLongitude("lambda1");
+ var lambda2Rad = InputLongitude("lambda2");
+
+ var cosFi1 = Math.Cos(fi1Rad);
+ var sinFi1 = Math.Sin(fi1Rad);
+
+ var cosFi2 = Math.Cos(fi2Rad);
+ var sinFi2 = Math.Sin(fi2Rad);
+
+ var a = cosFi1 * sinFi2 * Math.Cos(lambda1Rad);
+ var b = sinFi1 * cosFi2 * Math.Cos(lambda2Rad);
+ var c = sinFi1 * cosFi2 * Math.Sin(lambda2Rad);
+ var d = cosFi1 * sinFi2 * Math.Sin(lambda1Rad);
+ var lambdaP = Math.Atan2(a - b, c - d);
+
+ var fiP = Math.Atan2(-Math.Cos(lambdaP - lambda1Rad), Math.Tan(fi1Rad));
+ _cosFiP = Math.Cos(fiP);
+ _sinFiP = Math.Sin(fiP);
+ }
+
+ protected internal override void Project(double latitude, double longitude, out double x, out double y)
+ {
+ var sinLatitude = Math.Sin(latitude);
+ var cosLatitude = Math.Cos(latitude);
+
+ var sinLongitude = Math.Sin(longitude);
+ var cosLongitude = Math.Cos(longitude);
+
+ var a = _sinFiP * sinLatitude - _cosFiP * cosLatitude * sinLongitude; // sin_fi_p
+ if (double.IsNaN(a) || Math.Abs(a) > 1)
+ {
+ throw new ArgumentOutOfRangeException(string.Join(",", new[] { nameof(latitude), nameof(longitude) }));
+ }
+
+ x = MathX.Atan2(sinLatitude / cosLatitude * _cosFiP + sinLongitude * _sinFiP, cosLongitude, "latitude, longitude");
+ y = MathX.Clamp(MaxY, Math.Log((1 + a) / (1 - a)) / 2); // protect against +-Infinity
+ }
+
+ protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
+ {
+ var sinX = Math.Sin(x);
+ var cosX = Math.Cos(x);
+
+ var sinhY = Math.Sinh(y);
+ var coshY = Math.Cosh(y);
+
+ // cosh_y >= 1 by definition
+ var a = (_sinFiP * sinhY + _cosFiP * sinX) / coshY;
+ if (Math.Abs(a) > 1)
+ {
+ throw new ArgumentOutOfRangeException(string.Join(",",new []{nameof(x), nameof(y)}));
+ }
+
+ latitude = Math.Asin(a);
+ longitude = MathX.Atan2(_sinFiP * sinX - _cosFiP * sinhY, cosX, "x, y");
+ }
+
+ private readonly double _cosFiP; // = cos(fi_p)
+ private readonly double _sinFiP; // = sin(fi_p)
+ }
+}
\ No newline at end of file
diff --git a/Projections/Projection.cs b/src/Projections/Projection.cs
similarity index 61%
rename from Projections/Projection.cs
rename to src/Projections/Projection.cs
index 64203e6..c067a85 100644
--- a/Projections/Projection.cs
+++ b/src/Projections/Projection.cs
@@ -1,107 +1,96 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//
-// Purpose: Abstract base class of all projections.
-//------------------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using System.Text;
-
-namespace SQLSpatialTools
-{
- internal abstract class Projection
- {
- private readonly Dictionary _parameters = new Dictionary();
- private readonly double _longitude0; // in radians
-
- public double CentralLongitudeRad
- {
- get { return _longitude0; }
- }
-
- public string Parameters
- {
- get
- {
- StringBuilder a = new StringBuilder();
- foreach (string key in _parameters.Keys)
- {
- string value = _parameters[key].ToString("R", CultureInfo.InvariantCulture);
- if (a.Length > 0)
- {
- a.AppendFormat(";{0}={1}", key, value);
- }
- else
- {
- a.AppendFormat("{0}={1}", key, value);
- }
- }
- return a.ToString();
- }
- }
-
- public static Dictionary ParseParameters(string parameters)
- {
- Dictionary dict = new Dictionary();
- foreach (string pair in parameters.Split(';'))
- {
- string[] a = pair.Split('=');
- dict.Add(a[0], Double.Parse(a[1], NumberStyles.Float, CultureInfo.InvariantCulture));
- }
- return dict;
- }
-
- public Projection(Dictionary parameters)
- {
- Debug.Assert(parameters != null);
- _parameters = new Dictionary(parameters);
- _longitude0 = InputLongitude("longitude0");
- }
-
- // Used for validation and conversion of projection input parameters.
- //
- // Returns latitude converted to radians in interval (-Pi/2, Pi/2).
- // Throws ArgumentOutOfRangeException if latitude is NaN or not in range [-89.9, 89.9].
- //
- // Parram name: name of latitude argument in case if the exception is thrown
- //
- protected internal double InputLatitude(string name)
- {
- return InputLatitude(name, 89.9);
- }
-
- protected internal double InputLatitude(string name, double max)
- {
- return MathX.InputLat(_parameters[name], max, name);
- }
-
- // Used for validation and conversion of projection input parameters.
- //
- // Returns longitude converted to radians in range [-Pi, Pi).
- // Throws ArgumentOutOfRangeException if longitude is NaN or Infinity.
- //
- // Parram name: name of longitude argument in case if the exception is thrown
- //
- protected internal double InputLongitude(string name)
- {
- return InputLongitude(name, 360);
- }
-
- protected internal double InputLongitude(string name, double max)
- {
- return MathX.InputLong(_parameters[name], max, name);
- }
-
- // Longitude and latitude are in radians.
- // Latitude is assumed to be in range [-Pi/2, Pi/2].
- // Longitude is assumed to be in range [-Pi, Pi).
- protected internal abstract void Project(double latitude, double longitude, out double x, out double y);
-
- // Longitude and latitude are in radians.
- // Latitude must be in range [-Pi/2, Pi/2].
- // Longitude must be in range [-Pi, Pi).
- protected internal abstract void Unproject(double x, double y, out double latitude, out double longitude);
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Purpose: Abstract base class of all projections.
+//------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+
+namespace SQLSpatialTools.Projections
+{
+ internal abstract class Projection
+ {
+ private readonly Dictionary _parameters;
+
+ public double CentralLongitudeRad { get; }
+
+ public string Parameters
+ {
+ get
+ {
+ var a = new StringBuilder();
+ foreach (var key in _parameters.Keys)
+ {
+ var value = _parameters[key].ToString("R", CultureInfo.InvariantCulture);
+ a.AppendFormat(a.Length > 0 ? ";{0}={1}" : "{0}={1}", key, value);
+ }
+ return a.ToString();
+ }
+ }
+
+ public static Dictionary ParseParameters(string parameters)
+ {
+ return parameters.Split(';')
+ .Select(pair => pair.Split('='))
+ .ToDictionary(a => a[0],
+ a => double.Parse(a[1],
+ NumberStyles.Float,
+ CultureInfo.InvariantCulture));
+ }
+
+ protected Projection(IDictionary parameters)
+ {
+ Debug.Assert(parameters != null);
+ _parameters = new Dictionary(parameters);
+ CentralLongitudeRad = InputLongitude("longitude0");
+ }
+
+ // Used for validation and conversion of projection input parameters.
+ //
+ // Returns latitude converted to radians in interval (-Pi/2, Pi/2).
+ // Throws ArgumentOutOfRangeException if latitude is NaN or not in range [-89.9, 89.9].
+ //
+ // Param name: name of latitude argument in case if the exception is thrown
+ //
+ protected internal double InputLatitude(string name)
+ {
+ return InputLatitude(name, 89.9);
+ }
+
+ protected internal double InputLatitude(string name, double max)
+ {
+ return MathX.InputLat(_parameters[name], max, name);
+ }
+
+ // Used for validation and conversion of projection input parameters.
+ //
+ // Returns longitude converted to radians in range [-Pi, Pi).
+ // Throws ArgumentOutOfRangeException if longitude is NaN or Infinity.
+ //
+ // Param name: name of longitude argument in case if the exception is thrown
+ //
+ protected internal double InputLongitude(string name)
+ {
+ return InputLongitude(name, 360);
+ }
+
+ protected internal double InputLongitude(string name, double max)
+ {
+ return MathX.InputLong(_parameters[name], max, name);
+ }
+
+ // Longitude and latitude are in radians.
+ // Latitude is assumed to be in range [-Pi/2, Pi/2].
+ // Longitude is assumed to be in range [-Pi, Pi).
+ protected internal abstract void Project(double latitude, double longitude, out double x, out double y);
+
+ // Longitude and latitude are in radians.
+ // Latitude must be in range [-Pi/2, Pi/2].
+ // Longitude must be in range [-Pi, Pi).
+ protected internal abstract void Unproject(double x, double y, out double latitude, out double longitude);
+ }
}
\ No newline at end of file
diff --git a/Projections/TranverseMercatorProjection.cs b/src/Projections/TransverseMercatorProjection.cs
similarity index 85%
rename from Projections/TranverseMercatorProjection.cs
rename to src/Projections/TransverseMercatorProjection.cs
index c843007..38413ff 100644
--- a/Projections/TranverseMercatorProjection.cs
+++ b/src/Projections/TransverseMercatorProjection.cs
@@ -1,86 +1,87 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//
-// References: http://mathworld.wolfram.com/MercatorProjection.html
-//------------------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-
-namespace SQLSpatialTools
-{
- internal sealed class TranverseMercatorProjection : Projection
- {
- private const double MaxX = 10;
-
- // longitude0: Reference longitude.
- //
- public TranverseMercatorProjection(Dictionary parameters)
- : base(parameters)
- {
- }
-
- // x will be in range [-MaxX, MaxX]
- // y will be in range [-Pi, Pi]
- protected internal override void Project(double latitude, double longitude, out double x, out double y)
- {
- // North pole
- if (latitude >= Math.PI / 2 - MathX.Tolerance)
- {
- x = 0;
- y = Math.PI / 2;
- return;
- }
-
- // South pole
- if (latitude <= -Math.PI / 2 + MathX.Tolerance)
- {
- x = 0;
- y = -Math.PI / 2;
- return;
- }
-
- if (Math.Abs(latitude) <= MathX.Tolerance)
- {
- // East of India
- if (Math.Abs(longitude - Math.PI / 2) <= MathX.Tolerance)
- {
- x = MaxX;
- y = 0;
- return;
- }
- // West of South America
- if (Math.Abs(longitude + Math.PI / 2) <= MathX.Tolerance)
- {
- x = -MaxX;
- y = 0;
- return;
- }
- }
-
- double b = Math.Cos(latitude) * Math.Sin(longitude);
- x = MathX.Clamp(MaxX, Math.Log((1 + b) / (1 - b)) / 2);
- y = Math.Atan2(Math.Tan(latitude), Math.Cos(longitude));
- }
-
- protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
- {
- if (x >= MaxX)
- {
- latitude = 0;
- longitude = Math.PI / 2;
- }
- else if (x <= -MaxX)
- {
- latitude = 0;
- longitude = -Math.PI / 2;
- }
- else
- {
- // 1 <= cosh(x) <= cosh(MaxX)
- latitude = Math.Asin(Math.Sin(y) / Math.Cosh(x));
- // In case of x=0 and y=+-Pi/2: latitude will be +-90 and longtude will be 0
- longitude = MathX.Atan2(Math.Sinh(x), Math.Cos(y));
- }
- }
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// References: http://mathworld.wolfram.com/MercatorProjection.html
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+
+namespace SQLSpatialTools.Projections
+{
+ internal sealed class TransverseMercatorProjection : Projection
+ {
+ private const double MaxX = 10;
+
+ // longitude0: Reference longitude.
+ //
+ public TransverseMercatorProjection(IDictionary parameters)
+ : base(parameters)
+ {
+ }
+
+ // x will be in range [-MaxX, MaxX]
+ // y will be in range [-Pi, Pi]
+ protected internal override void Project(double latitude, double longitude, out double x, out double y)
+ {
+ // North pole
+ if (latitude >= Math.PI / 2 - MathX.Tolerance)
+ {
+ x = 0;
+ y = Math.PI / 2;
+ return;
+ }
+
+ // South pole
+ if (latitude <= -Math.PI / 2 + MathX.Tolerance)
+ {
+ x = 0;
+ y = -Math.PI / 2;
+ return;
+ }
+
+ if (Math.Abs(latitude) <= MathX.Tolerance)
+ {
+ // East of India
+ if (Math.Abs(longitude - Math.PI / 2) <= MathX.Tolerance)
+ {
+ x = MaxX;
+ y = 0;
+ return;
+ }
+ // West of South America
+ if (Math.Abs(longitude + Math.PI / 2) <= MathX.Tolerance)
+ {
+ x = -MaxX;
+ y = 0;
+ return;
+ }
+ }
+
+ double b = Math.Cos(latitude) * Math.Sin(longitude);
+ x = MathX.Clamp(MaxX, Math.Log((1 + b) / (1 - b)) / 2);
+ y = Math.Atan2(Math.Tan(latitude), Math.Cos(longitude));
+ }
+
+ protected internal override void Unproject(double x, double y, out double latitude, out double longitude)
+ {
+ if (x >= MaxX)
+ {
+ latitude = 0;
+ longitude = Math.PI / 2;
+ }
+ else if (x <= -MaxX)
+ {
+ latitude = 0;
+ longitude = -Math.PI / 2;
+ }
+ else
+ {
+ // 1 <= cosh(x) <= cosh(MaxX)
+ latitude = Math.Asin(Math.Sin(y) / Math.Cosh(x));
+ // In case of x=0 and y=+-Pi/2: latitude will be +-90 and longitude will be 0
+ longitude = MathX.Atan2(Math.Sinh(x), Math.Cos(y));
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..0b7b6d1
--- /dev/null
+++ b/src/Properties/AssemblyInfo.cs
@@ -0,0 +1,43 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SQLSpatialTools")]
+[assembly: AssemblyDescription("A collection of tools for use with the SQL Server spatial functionality.")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("SQLSpatialTools")]
+[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: InternalsVisibleTo("SQLSpatialTools.UnitTests, PublicKey=00240000048000009400000006020000002400005253413100040000010001007d94bba1d58b134e06b24a4a03215f8fc90fe33bfe8ec3be86c0d1f26a87809ec4eb032925180a9a30c4890d480d13dff052c2ee5ae89f14f32b9bf4306e436ce5c79b53c3517e758b7db776b13a6f85e95e8267aa1df08c65bdae1453abe0eb33e625af0d18acb2aefa5460f25af0920ae310e8c1301bf5ecd7130762aa70d2")]
+[assembly: InternalsVisibleTo("SQLSpatialTools.UnitTests.DDD, PublicKey=00240000048000009400000006020000002400005253413100040000010001007d94bba1d58b134e06b24a4a03215f8fc90fe33bfe8ec3be86c0d1f26a87809ec4eb032925180a9a30c4890d480d13dff052c2ee5ae89f14f32b9bf4306e436ce5c79b53c3517e758b7db776b13a6f85e95e8267aa1df08c65bdae1453abe0eb33e625af0d18acb2aefa5460f25af0920ae310e8c1301bf5ecd7130762aa70d2")]
+[assembly: InternalsVisibleTo("SQLSpatialTools.UnitTests.Extension, PublicKey=00240000048000009400000006020000002400005253413100040000010001007d94bba1d58b134e06b24a4a03215f8fc90fe33bfe8ec3be86c0d1f26a87809ec4eb032925180a9a30c4890d480d13dff052c2ee5ae89f14f32b9bf4306e436ce5c79b53c3517e758b7db776b13a6f85e95e8267aa1df08c65bdae1453abe0eb33e625af0d18acb2aefa5460f25af0920ae310e8c1301bf5ecd7130762aa70d2")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("ee2e7fb3-3d63-4eb8-b0a7-8b089f5e2cf3")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.3.0")]
+[assembly: AssemblyFileVersion("1.0.3.0")]
diff --git a/Resource.Designer.cs b/src/Resource/Resource.Designer.cs
similarity index 97%
rename from Resource.Designer.cs
rename to src/Resource/Resource.Designer.cs
index 4836a03..9c6dc1b 100644
--- a/Resource.Designer.cs
+++ b/src/Resource/Resource.Designer.cs
@@ -1,118 +1,118 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:2.0.50727.1433
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace SQLSpatialTools
-{
- using System;
-
-
- ///
- /// A strongly-typed resource class, for looking up localized strings, etc.
- ///
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // class via a tool like ResGen or Visual Studio.
- // To add or remove a member, edit your .ResX file then rerun ResGen
- // with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resource {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resource() {
- }
-
- ///
- /// Returns the cached ResourceManager instance used by this class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager {
- get {
- if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.SqlServer.SpatialToolbox.Resource", typeof(Resource).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- ///
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
-
- ///
- /// Looks up a localized string similar to fi1 and fi2 must be different.
- ///
- internal static string Fi1AndFi2MustBeDifferent {
- get {
- return ResourceManager.GetString("Fi1AndFi2MustBeDifferent", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Input coordinate is NaN..
- ///
- internal static string InputCoordinateIsNaN {
- get {
- return ResourceManager.GetString("InputCoordinateIsNaN", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Input latitude {0} is out of range [-{1}, {1}]..
- ///
- internal static string InputLatitudeIsOutOfRange {
- get {
- return ResourceManager.GetString("InputLatitudeIsOutOfRange", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Input longitude {0} is out of range [-{1}, {1}]..
- ///
- internal static string InputLongitudeIsOutOfRange {
- get {
- return ResourceManager.GetString("InputLongitudeIsOutOfRange", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Output latitude {0} is out of range [-Pi/2, Pi/2]..
- ///
- internal static string OutputLatitudeIsOutOfRange {
- get {
- return ResourceManager.GetString("OutputLatitudeIsOutOfRange", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Output longitude {0} is out of range [-Pi, Pi)..
- ///
- internal static string OutputLongitudeIsOutOfRange {
- get {
- return ResourceManager.GetString("OutputLongitudeIsOutOfRange", resourceCulture);
- }
- }
- }
-}
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:2.0.50727.1433
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace SQLSpatialTools
+{
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resource {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resource() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.SqlServer.SpatialToolbox.Resource", typeof(Resource).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to fi1 and fi2 must be different.
+ ///
+ internal static string Fi1AndFi2MustBeDifferent {
+ get {
+ return ResourceManager.GetString("Fi1AndFi2MustBeDifferent", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Input coordinate is NaN..
+ ///
+ internal static string InputCoordinateIsNaN {
+ get {
+ return ResourceManager.GetString("InputCoordinateIsNaN", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Input latitude {0} is out of range [-{1}, {1}]..
+ ///
+ internal static string InputLatitudeIsOutOfRange {
+ get {
+ return ResourceManager.GetString("InputLatitudeIsOutOfRange", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Input longitude {0} is out of range [-{1}, {1}]..
+ ///
+ internal static string InputLongitudeIsOutOfRange {
+ get {
+ return ResourceManager.GetString("InputLongitudeIsOutOfRange", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Output latitude {0} is out of range [-Pi/2, Pi/2]..
+ ///
+ internal static string OutputLatitudeIsOutOfRange {
+ get {
+ return ResourceManager.GetString("OutputLatitudeIsOutOfRange", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Output longitude {0} is out of range [-Pi, Pi)..
+ ///
+ internal static string OutputLongitudeIsOutOfRange {
+ get {
+ return ResourceManager.GetString("OutputLongitudeIsOutOfRange", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Resource.resx b/src/Resource/Resource.resx
similarity index 97%
rename from Resource.resx
rename to src/Resource/Resource.resx
index 1c57ccd..6f7e0de 100644
--- a/Resource.resx
+++ b/src/Resource/Resource.resx
@@ -1,138 +1,138 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- fi1 and fi2 must be different
-
-
- Input coordinate is NaN.
-
-
- Input latitude {0} is out of range [-{1}, {1}].
-
-
- Input longitude {0} is out of range [-{1}, {1}].
-
-
- Output latitude {0} is out of range [-Pi/2, Pi/2].
-
-
- Output longitude {0} is out of range [-Pi, Pi).
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ fi1 and fi2 must be different
+
+
+ Input coordinate is NaN.
+
+
+ Input latitude {0} is out of range [-{1}, {1}].
+
+
+ Input longitude {0} is out of range [-{1}, {1}].
+
+
+ Output latitude {0} is out of range [-Pi/2, Pi/2].
+
+
+ Output longitude {0} is out of range [-Pi, Pi).
+
\ No newline at end of file
diff --git a/src/SQL Scripts/Register.sql b/src/SQL Scripts/Register.sql
new file mode 100644
index 0000000..aaf81ca
--- /dev/null
+++ b/src/SQL Scripts/Register.sql
@@ -0,0 +1,401 @@
+-- Copyright (c) Microsoft Corporation. All rights reserved.
+-- Install the SQLSpatialTools assembly and all its functions into the current database
+
+-- Enabling CLR prior to registering assembly and its related functions.
+EXEC sp_configure 'show advanced option', '1';
+RECONFIGURE;
+Go
+
+sp_configure 'clr enabled', 1 ;
+GO
+RECONFIGURE ;
+GO
+
+EXEC sp_configure 'clr strict security', '0'
+RECONFIGURE WITH OVERRIDE;
+GO
+
+-- !!! DLL Path will be replace based upon system environment !!!
+CREATE assembly SQLSpatialTools
+FROM 'DLLPath'
+GO
+
+-- Create User Defined SQL Types
+CREATE TYPE Projection EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Types.SQL.SqlProjection]
+GO
+
+CREATE TYPE AffineTransform EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Types.SQL.AffineTransform]
+GO
+
+-- Register the functions...
+
+--#region General Geometry Functions
+CREATE FUNCTION FilterArtifactsGeometry (
+ @geometry GEOMETRY
+ ,@filterEmptyShapes BIT
+ ,@filterPoints BIT
+ ,@lineStringTolerance FLOAT(53)
+ ,@ringTolerance FLOAT(53)
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geometry].FilterArtifactsGeometry
+GO
+
+CREATE FUNCTION GeomFromXYMText (
+ @geometry NVARCHAR(MAX)
+ ,@targetSrid INT
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geometry].GeomFromXYMText
+GO
+
+CREATE FUNCTION InterpolateBetweenGeom (
+ @startPoint GEOMETRY
+ ,@endPoint GEOMETRY
+ ,@distance FLOAT
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geometry].InterpolateBetweenGeom
+GO
+
+CREATE FUNCTION LocateAlongGeom (
+ @geometry GEOMETRY
+ ,@distance FLOAT
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geometry].LocatePointAlongGeom
+GO
+
+CREATE FUNCTION MakeValidForGeography (@geometry GEOMETRY)
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geometry].MakeValidForGeography
+GO
+
+CREATE FUNCTION ReverseLinestring (@geometry GEOMETRY)
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geometry].ReverseLinestring
+GO
+
+CREATE FUNCTION ShiftGeometry (
+ @geometry GEOMETRY
+ ,@xShift FLOAT
+ ,@yShift FLOAT
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geometry].ShiftGeometry
+GO
+
+CREATE FUNCTION VacuousGeometryToGeography (
+ @geometryToConvert GEOMETRY
+ ,@targetSrid INT
+ )
+RETURNS GEOGRAPHY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geometry].VacuousGeometryToGeography
+GO
+
+--#endregion
+
+--#region General Geography Functions
+
+CREATE FUNCTION ConvexHullGeographyFromText (
+ @inputWKT NVARCHAR(max)
+ ,@srid INT
+ )
+RETURNS GEOGRAPHY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geography].ConvexHullGeographyFromText
+GO
+
+CREATE FUNCTION ConvexHullGeography (@geography GEOGRAPHY)
+RETURNS GEOGRAPHY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geography].ConvexHullGeography
+GO
+
+CREATE FUNCTION DensifyGeography (
+ @geography GEOGRAPHY
+ ,@maxAngle FLOAT
+ )
+RETURNS GEOGRAPHY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geography].DensifyGeography
+GO
+
+CREATE FUNCTION FilterArtifactsGeography (
+ @geography GEOGRAPHY
+ ,@filterEmptyShapes BIT
+ ,@filterPoints BIT
+ ,@lineStringTolerance FLOAT(53)
+ ,@ringTolerance FLOAT(53)
+ )
+RETURNS GEOGRAPHY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geography].FilterArtifactsGeography
+GO
+
+CREATE FUNCTION InterpolateBetweenGeog (
+ @startPoint GEOGRAPHY
+ ,@endPoint GEOGRAPHY
+ ,@distance FLOAT
+ )
+RETURNS GEOGRAPHY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geography].InterpolateBetweenGeog
+GO
+
+CREATE FUNCTION IsValidGeographyFromGeometry (@geometry GEOMETRY)
+RETURNS BIT
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geography].IsValidGeographyFromGeometry
+GO
+
+CREATE FUNCTION IsValidGeographyFromText (
+ @inputWKT NVARCHAR(MAX)
+ ,@srid INT
+ )
+RETURNS BIT
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geography].IsValidGeographyFromText
+GO
+
+CREATE FUNCTION LocateAlongGeog (
+ @geography GEOGRAPHY
+ ,@distance FLOAT
+ )
+RETURNS GEOGRAPHY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geography].LocatePointAlongGeog
+GO
+
+CREATE FUNCTION MakeValidGeographyFromGeometry (@geometry GEOMETRY)
+RETURNS GEOGRAPHY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geography].MakeValidGeographyFromGeometry
+GO
+
+CREATE FUNCTION MakeValidGeographyFromText (
+ @inputWKT NVARCHAR(MAX)
+ ,@srid INT
+ )
+RETURNS GEOGRAPHY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geography].MakeValidGeographyFromText
+GO
+
+CREATE FUNCTION VacuousGeographyToGeometry (
+ @geographyToConvert GEOGRAPHY
+ ,@targetSrid INT
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.General.Geography].VacuousGeographyToGeometry
+GO
+
+--#endregion
+
+--#region LRS Geometric Functions
+CREATE FUNCTION LRS_ClipGeometrySegment (
+ @geometry GEOMETRY
+ ,@startMeasure FLOAT
+ ,@endMeasure FLOAT
+ ,@tolerance FLOAT(53)
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].ClipGeometrySegment
+GO
+
+CREATE FUNCTION LRS_ConvertToLrsGeom (
+ @geometry GEOMETRY
+ ,@startMeasure FLOAT
+ ,@endMeasure FLOAT
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].ConvertToLrsGeom
+GO
+
+CREATE FUNCTION LRS_GetEndMeasure (@geometry GEOMETRY)
+RETURNS FLOAT
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].GetEndMeasure
+GO
+
+CREATE FUNCTION LRS_GetMergePosition (
+ @geometry1 GEOMETRY
+ ,@geometry2 GEOMETRY
+ ,@tolerance FLOAT(53)
+ )
+RETURNS NVARCHAR(20)
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].GetMergePosition
+GO
+
+CREATE FUNCTION LRS_GetStartMeasure (@geometry GEOMETRY)
+RETURNS FLOAT
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].GetStartMeasure
+GO
+
+CREATE FUNCTION LRS_InterpolateBetweenGeom (
+ @startPoint GEOMETRY
+ ,@endPoint GEOMETRY
+ ,@measure FLOAT
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].InterpolateBetweenGeom
+GO
+
+CREATE FUNCTION LRS_IsConnected (
+ @geometry1 GEOMETRY
+ ,@geometry2 GEOMETRY
+ ,@tolerance FLOAT(53)
+ )
+RETURNS BIT
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].IsConnected
+GO
+
+CREATE FUNCTION LRS_IsValidPoint (
+ @geometry GEOMETRY
+ )
+RETURNS BIT
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].IsValidPoint
+GO
+
+CREATE FUNCTION LRS_LocatePointAlongGeom (
+ @geometry GEOMETRY
+ ,@distance FLOAT
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].LocatePointAlongGeom
+GO
+
+CREATE FUNCTION LRS_MergeGeometrySegments (
+ @geometry1 GEOMETRY
+ ,@geometry2 GEOMETRY
+ ,@tolerance FLOAT(53)
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].MergeGeometrySegments
+GO
+
+CREATE FUNCTION LRS_MergeAndResetGeometrySegments (
+ @geometry1 GEOMETRY
+ ,@geometry2 GEOMETRY
+ ,@tolerance FLOAT(53)
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].MergeAndResetGeometrySegments
+GO
+
+CREATE FUNCTION LRS_OffsetGeometrySegments (
+ @geometry GEOMETRY
+ ,@startMeasure FLOAT
+ ,@endMeasure FLOAT
+ ,@offset FLOAT
+ ,@tolerance FLOAT(53)
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].OffsetGeometrySegment
+GO
+
+CREATE FUNCTION LRS_PopulateGeometryMeasures (
+ @geometry GEOMETRY
+ ,@startMeasure FLOAT
+ ,@endMeasure FLOAT
+ )
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].PopulateGeometryMeasures
+GO
+
+CREATE FUNCTION LRS_ResetMeasure(@geometry GEOMETRY)
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].ResetMeasure
+GO
+
+CREATE FUNCTION LRS_ReverseLinearGeometry (@geometry GEOMETRY)
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].ReverseLinearGeometry
+GO
+
+CREATE FUNCTION LRS_ScaleGeometrySegment (@geometry GEOMETRY
+ ,@startMeasure FLOAT
+ ,@endMeasure FLOAT
+ ,@shiftMeasure FLOAT)
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].ScaleGeometrySegment
+GO
+
+CREATE PROCEDURE LRS_SplitGeometrySegment @geometry GEOMETRY
+ ,@splitMeasure FLOAT
+ ,@geometry1 GEOMETRY OUTPUT
+ ,@geometry2 GEOMETRY OUTPUT
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].SplitGeometrySegment
+GO
+
+CREATE FUNCTION LRS_TranslateMeasure (@geometry GEOMETRY
+ ,@translateMeasure FLOAT)
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].TranslateMeasure
+GO
+
+CREATE FUNCTION LRS_ValidateLRSGeometry (@geometry GEOMETRY)
+RETURNS NVARCHAR(10)
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.LRS.Geometry].ValidateLRSGeometry
+GO
+
+CREATE FUNCTION Util_PolygonToLine (@geometry GEOMETRY)
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.Util.Geometry].PolygonToLine
+GO
+
+CREATE FUNCTION Util_Extract (@geometry GEOMETRY
+ ,@elementIndex INTEGER
+ ,@ringIndex INTEGER)
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.Util.Geometry].ExtractGeometry
+GO
+
+CREATE FUNCTION Util_RemoveDuplicateVertices (@geometry GEOMETRY, @tolerance FLOAT)
+RETURNS GEOMETRY
+AS
+EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Functions.Util.Geometry].RemoveDuplicateVertices
+GO
+--#endregion
+
+-- Create aggregates.
+CREATE AGGREGATE GEOMETRYEnvelopeAggregate (@geometry GEOMETRY)
+RETURNS GEOMETRY EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Aggregates.GeometryEnvelopeAggregate]
+GO
+
+CREATE AGGREGATE GeographyCollectionAggregate (@geography GEOGRAPHY)
+RETURNS GEOGRAPHY EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Aggregates.GeographyCollectionAggregate]
+GO
+
+CREATE AGGREGATE GeographyUnionAggregate (@geography GEOGRAPHY)
+RETURNS GEOGRAPHY EXTERNAL NAME SQLSpatialTools.[SQLSpatialTools.Aggregates.GeographyUnionAggregate]
+GO
\ No newline at end of file
diff --git a/src/SQL Scripts/RegisterAndValidate.sql b/src/SQL Scripts/RegisterAndValidate.sql
new file mode 100644
index 0000000..628efd6
--- /dev/null
+++ b/src/SQL Scripts/RegisterAndValidate.sql
@@ -0,0 +1,25 @@
+-- Copyright (c) Microsoft Corporation. All rights reserved.
+-- Install the SQLSpatialTools assembly and all its functions into the current database
+-- Runs all the LRS functions with sample examples.
+
+-- First unregister should be called
+-- To run sql script inside another SQLCMD Mode should be enabled
+-- To enable SQLCMD Mode: Query -> SQLCMD Mode
+
+-- First Unregister existing functions
+:r "ScriptDirPath\Unregister.sql"
+
+-- Register Script Path will be replaced in run time
+:r "ScriptDirPath\Register.sql"
+
+-- Run LRS Functions
+:r "ScriptDirPath\lrs_geometry_example.sql"
+
+-- Run Util Functions
+:r "ScriptDirPath\util_geometry_example.sql"
+
+-- Run Sample Projections
+:r "ScriptDirPath\projection_example.sql"
+
+-- Run Sample Transforms
+:r "ScriptDirPath\transform_example.sql"
\ No newline at end of file
diff --git a/src/SQL Scripts/Unregister.sql b/src/SQL Scripts/Unregister.sql
new file mode 100644
index 0000000..ab7fe23
--- /dev/null
+++ b/src/SQL Scripts/Unregister.sql
@@ -0,0 +1,150 @@
+--WARNING! ERRORS ENCOUNTERED DURING SQL PARSING!
+-- Copyright (c) Microsoft Corporation. All rights reserved.
+-- Drop the SQLSpatialTools assembly and all its functions from the current database
+-- Drop the aggregates...
+IF OBJECT_ID('GeometryEnvelopeAggregate') IS NOT NULL
+ DROP AGGREGATE GeometryEnvelopeAggregate
+
+IF OBJECT_ID('GeographyCollectionAggregate') IS NOT NULL
+ DROP AGGREGATE GeographyCollectionAggregate
+
+IF OBJECT_ID('GeographyUnionAggregate') IS NOT NULL
+ DROP AGGREGATE GeographyUnionAggregate
+
+-- Drop the functions...
+-- General Geometry
+IF OBJECT_ID('FilterArtifactsGeometry') IS NOT NULL
+ DROP FUNCTION FilterArtifactsGeometry
+
+IF OBJECT_ID('GeomFromXYMText') IS NOT NULL
+ DROP FUNCTION GeomFromXYMText
+
+IF OBJECT_ID('InterpolateBetweenGeom') IS NOT NULL
+ DROP FUNCTION InterpolateBetweenGeom
+
+IF OBJECT_ID('LocateAlongGeom') IS NOT NULL
+ DROP FUNCTION LocateAlongGeom
+
+IF OBJECT_ID('MakeValidForGeography') IS NOT NULL
+ DROP FUNCTION MakeValidForGeography
+
+IF OBJECT_ID('ReverseLinestring') IS NOT NULL
+ DROP FUNCTION ReverseLinestring
+
+IF OBJECT_ID('ShiftGeometry') IS NOT NULL
+ DROP FUNCTION ShiftGeometry
+
+IF OBJECT_ID('VacuousGeometryToGeography') IS NOT NULL
+ DROP FUNCTION VacuousGeometryToGeography
+
+-- General Geography
+IF OBJECT_ID('ConvexHullGeographyFromText') IS NOT NULL
+ DROP FUNCTION ConvexHullGeographyFromText
+
+IF OBJECT_ID('ConvexHullGeography') IS NOT NULL
+ DROP FUNCTION ConvexHullGeography
+
+IF OBJECT_ID('DensifyGeography') IS NOT NULL
+ DROP FUNCTION DensifyGeography
+
+IF OBJECT_ID('FilterArtifactsGeography') IS NOT NULL
+ DROP FUNCTION FilterArtifactsGeography
+
+IF OBJECT_ID('InterpolateBetweenGeog') IS NOT NULL
+ DROP FUNCTION InterpolateBetweenGeog
+
+IF OBJECT_ID('IsValidGeographyFromGeometry') IS NOT NULL
+ DROP FUNCTION IsValidGeographyFromGeometry
+
+IF OBJECT_ID('IsValidGeographyFromText') IS NOT NULL
+ DROP FUNCTION IsValidGeographyFromText
+
+IF OBJECT_ID('LocateAlongGeog') IS NOT NULL
+ DROP FUNCTION LocateAlongGeog
+
+IF OBJECT_ID('MakeValidGeographyFromGeometry') IS NOT NULL
+ DROP FUNCTION MakeValidGeographyFromGeometry
+
+IF OBJECT_ID('MakeValidGeographyFromText') IS NOT NULL
+ DROP FUNCTION MakeValidGeographyFromText
+
+IF OBJECT_ID('VacuousGeographyToGeometry') IS NOT NULL
+ DROP FUNCTION VacuousGeographyToGeometry
+
+-- LRS Geometry
+IF OBJECT_ID('LRS_ClipGeometrySegment') IS NOT NULL
+ DROP FUNCTION LRS_ClipGeometrySegment
+
+IF OBJECT_ID('LRS_ConvertToLrsGeom') IS NOT NULL
+DROP FUNCTION LRS_ConvertToLrsGeom
+
+IF OBJECT_ID('LRS_GetEndMeasure') IS NOT NULL
+ DROP FUNCTION LRS_GetEndMeasure
+
+IF OBJECT_ID('LRS_GetMergePosition') IS NOT NULL
+ DROP FUNCTION LRS_GetMergePosition
+
+IF OBJECT_ID('LRS_GetStartMeasure') IS NOT NULL
+ DROP FUNCTION LRS_GetStartMeasure
+
+IF OBJECT_ID('LRS_InterpolateBetweenGeom') IS NOT NULL
+ DROP FUNCTION LRS_InterpolateBetweenGeom
+
+IF OBJECT_ID('LRS_IsConnected') IS NOT NULL
+ DROP FUNCTION LRS_IsConnected
+
+IF OBJECT_ID('LRS_IsValidPoint') IS NOT NULL
+ DROP FUNCTION LRS_IsValidPoint
+
+IF OBJECT_ID('LRS_LocatePointAlongGeom') IS NOT NULL
+ DROP FUNCTION LRS_LocatePointAlongGeom
+
+IF OBJECT_ID('LRS_MergeGeometrySegments') IS NOT NULL
+ DROP FUNCTION LRS_MergeGeometrySegments
+
+IF OBJECT_ID('LRS_MergeAndResetGeometrySegments') IS NOT NULL
+ DROP FUNCTION LRS_MergeAndResetGeometrySegments
+
+IF OBJECT_ID('LRS_OffsetGeometrySegments') IS NOT NULL
+ DROP FUNCTION LRS_OffsetGeometrySegments
+
+IF OBJECT_ID('LRS_PopulateGeometryMeasures') IS NOT NULL
+ DROP FUNCTION LRS_PopulateGeometryMeasures
+
+IF OBJECT_ID('LRS_ResetMeasure') IS NOT NULL
+ DROP FUNCTION LRS_ResetMeasure
+
+IF OBJECT_ID('LRS_ReverseLinearGeometry') IS NOT NULL
+ DROP FUNCTION LRS_ReverseLinearGeometry
+
+IF OBJECT_ID('LRS_ScaleGeometrySegment') IS NOT NULL
+ DROP FUNCTION LRS_ScaleGeometrySegment
+
+IF OBJECT_ID('LRS_SplitGeometrySegment') IS NOT NULL
+ DROP PROCEDURE LRS_SplitGeometrySegment
+
+IF OBJECT_ID('LRS_TranslateMeasure') IS NOT NULL
+ DROP FUNCTION LRS_TranslateMeasure
+
+IF OBJECT_ID('LRS_ValidateLRSGeometry') IS NOT NULL
+ DROP FUNCTION LRS_ValidateLRSGeometry
+
+-- Utility Functions
+IF OBJECT_ID('Util_PolygonToLine') IS NOT NULL
+ DROP FUNCTION Util_PolygonToLine
+
+IF OBJECT_ID('Util_RemoveDuplicateVertices') IS NOT NULL
+ DROP FUNCTION Util_RemoveDuplicateVertices
+
+IF OBJECT_ID('Util_Extract') IS NOT NULL
+ DROP FUNCTION Util_Extract
+
+-- Drop the types...
+IF TYPE_ID('Projection') IS NOT NULL
+ DROP TYPE Projection
+
+IF TYPE_ID('AffineTransform') IS NOT NULL
+ DROP TYPE AffineTransform
+
+-- Drop the assembly...
+DROP ASSEMBLY IF EXISTS SQLSpatialTools
diff --git a/SQL Scripts/aggregate_example.sql b/src/SQL Scripts/aggregate_example.sql
similarity index 98%
rename from SQL Scripts/aggregate_example.sql
rename to src/SQL Scripts/aggregate_example.sql
index 4ab178d..1902fb0 100644
--- a/SQL Scripts/aggregate_example.sql
+++ b/src/SQL Scripts/aggregate_example.sql
@@ -1,7 +1,7 @@
--- Script assumes that current database contains zipcodes table with columns: code, state, shape_geom, shape_geog
-
-select state, dbo.GeometryEnvelopeAggregate(shape_geom) from zipcodes group by state;
-
--- For every state return its polygon by performing an union of all zip area belonging to that state.
--- Also buffer result by 0.5 meters to avoid tiny cracks along the borders of zip areas.
+-- Script assumes that current database contains zipcodes table with columns: code, state, shape_geom, shape_geog
+
+select state, dbo.GeometryEnvelopeAggregate(shape_geom) from zipcodes group by state;
+
+-- For every state return its polygon by performing an union of all zip area belonging to that state.
+-- Also buffer result by 0.5 meters to avoid tiny cracks along the borders of zip areas.
select state, dbo.GeographyUnionAggregate(shape_geog).STBuffer(0.5) from zipcodes group by state;
\ No newline at end of file
diff --git a/src/SQL Scripts/lrs_geometry_example.sql b/src/SQL Scripts/lrs_geometry_example.sql
new file mode 100644
index 0000000..0e4079b
--- /dev/null
+++ b/src/SQL Scripts/lrs_geometry_example.sql
@@ -0,0 +1,176 @@
+-- Script that shows examples on exposed LRS Functions in SQLSpatialTools Library
+-- LRS Geometric Functions
+DECLARE @geom geometry;
+DECLARE @geom1 geometry
+DECLARE @geom2 geometry
+DECLARE @srid INT = 4326;
+DECLARE @distance FLOAT;
+DECLARE @measure FLOAT;
+DECLARE @offset FLOAT = 2;
+DECLARE @tolerance FLOAT = 0.5;
+DECLARE @startMeasure FLOAT = 15.0;
+DECLARE @endMeasure FLOAT = 20.0;
+
+SET @geom = GEOMETRY::STGeomFromText('LINESTRING (20 1 NULL 10, 25 1 NULL 25 )', @srid);
+
+-- 1. ClipGeometrySegement Function
+SELECT 'Clipped Segment' AS 'FunctionInfo'
+ ,[dbo].[LRS_ClipGeometrySegment](@geom, @startMeasure, @endMeasure, @tolerance) AS 'Geometry'
+ ,[dbo].[LRS_ClipGeometrySegment](@geom, @startMeasure, @endMeasure, @tolerance).ToString() AS 'Geometry in String'
+
+-- 2. Get Start Measure
+SELECT 'Start Measure' AS 'FunctionInfo'
+ ,[dbo].[LRS_GetStartMeasure](@geom) AS 'Measure'
+
+-- 3. Get Merge Position
+SET @geom1 = GEOMETRY::STGeomFromText('LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)', @srid);
+SET @geom2 = GEOMETRY::STGeomFromText('LINESTRING(5 5 0 0, 2 2 0 0)', @srid);
+SET @tolerance = 0.5;
+
+SELECT 'Merge Position' AS 'FunctionInfo'
+ ,[dbo].LRS_GetMergePosition(@geom1, @geom2, @tolerance) AS 'IsConnected';
+
+-- 4. Get End Measure
+SELECT 'End Measure' AS 'FunctionInfo'
+ ,[dbo].[LRS_GetEndMeasure](@geom) AS 'Measure'
+
+-- 5. Interpolate points between Geom
+SET @geom1 = GEOMETRY::STGeomFromText('POINT(0 0 0 0)', @srid);
+SET @geom2 = GEOMETRY::STGeomFromText('POINT(10 0 0 10)', @srid);
+
+SET @measure = 5;
+
+SELECT 'Interpolate Points' AS 'FunctionInfo'
+ ,[dbo].[LRS_InterpolateBetweenGeom](@geom1, @geom2, @measure) AS 'Geometry'
+ ,[dbo].[LRS_InterpolateBetweenGeom](@geom1, @geom2, @measure).ToString() AS 'Geometry in String';
+
+-- 6. Is Spatially Connected
+SET @geom1 = GEOMETRY::STGeomFromText('LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)', @srid);
+SET @geom2 = GEOMETRY::STGeomFromText('LINESTRING(5 5 0 0, 2 2 0 0)', @srid);
+SET @tolerance = 0.5;
+
+SELECT 'Is Spatially Connected' AS 'FunctionInfo'
+ ,[dbo].[LRS_IsConnected](@geom1, @geom2, @tolerance) AS 'IsConnected';
+
+-- 7. IsValid LRS Point
+SET @geom = GEOMETRY::STGeomFromText('POINT(0 0 0)', @srid);
+
+SELECT 'Is Valid LRS Point' AS 'FunctionInfo'
+ ,[dbo].[LRS_IsValidPoint](@geom) AS 'IsValidLRSPoint';
+
+-- 8. Locate Point Along the Geometry Segment
+SET @geom = GEOMETRY::STGeomFromText('LINESTRING (0 0 0 0, 10 0 0 10)', @srid);
+SET @measure = 5.0;
+
+SELECT 'Point to Locate' AS 'FunctionInfo'
+ ,[dbo].[LRS_LocatePointAlongGeom](@geom, @measure) AS 'Geometry'
+ ,[dbo].[LRS_LocatePointAlongGeom](@geom, @measure).ToString() AS 'Geometry in String';
+
+-- 9. Merge two Geometry Segments to one Geometry Segment.
+SET @geom1 = GEOMETRY::STGeomFromText('LINESTRING (10 1 NULL 10, 25 1 NULL 25)', @srid);
+SET @geom2 = GEOMETRY::STGeomFromText('LINESTRING (30 1 NULL 30, 40 1 NULL 40 )', @srid);
+
+SELECT 'Merge Geometry Segments' AS 'FunctionInfo'
+ ,[dbo].[LRS_MergeGeometrySegments](@geom1, @geom2, @tolerance) AS 'Geometry'
+ ,[dbo].[LRS_MergeGeometrySegments](@geom1, @geom2, @tolerance).ToString() AS 'Geometry in String';
+
+-- 10. Merge two Geometry Segments to one Geometry Segment and reset there measures
+SET @geom1 = GEOMETRY::STGeomFromText('LINESTRING (10 15 9, 10 14 12, 10 10 20)', @srid);
+SET @geom2 = GEOMETRY::STGeomFromText('LINESTRING (9.5 14.5 100, 10 18 10, 10 10 5)', @srid);
+
+SELECT 'Merge and Reset Geometry Segments' AS 'FunctionInfo'
+ ,[dbo].[LRS_MergeAndResetGeometrySegments](@geom1, @geom2, @tolerance) AS 'Geometry'
+ ,[dbo].[LRS_MergeAndResetGeometrySegments](@geom1, @geom2, @tolerance).ToString() AS 'Geometry in String';
+
+-- 11. OffsetGeometrySegment Function
+SET @geom = GEOMETRY::STGeomFromText('LINESTRING(5 10 0, 20 5 30.628, 35 10 61.257, 55 10 100)', @srid);
+SET @startMeasure = 0;
+SET @endMeasure = 61.5;
+SET @offset = 2;
+SELECT 'Offset Segment' AS 'FunctionInfo'
+ ,[dbo].[LRS_OffsetGeometrySegments](@geom, @startMeasure, @endMeasure, @offset, @tolerance) AS 'Geometry'
+ ,[dbo].[LRS_OffsetGeometrySegments](@geom, @startMeasure, @endMeasure, @offset, @tolerance).ToString() AS 'Geometry in String'
+
+-- 12. Populate geometry measures.
+SET @startMeasure = 10;
+SET @endMeasure = 40;
+SET @geom = GEOMETRY::STGeomFromText('LINESTRING (10 1 10 100, 15 1 10 NULL, 20 1 10 NULL, 25 1 10 250 )', @srid);
+
+SELECT 'Populate Geometric Measures' AS 'FunctionInfo'
+ ,[dbo].[LRS_PopulateGeometryMeasures](@geom, @startMeasure, @endMeasure) AS 'Geometry'
+ ,[dbo].[LRS_PopulateGeometryMeasures](@geom, @startMeasure, @endMeasure).ToString() AS 'Geometry in String';
+
+-- 13. Convert To Lrs Geometry
+SET @startMeasure = 10;
+SET @endMeasure = 40;
+SET @geom = GEOMETRY::STGeomFromText('LINESTRING (10 1 , 15 1, 20 1, 25 1)', @srid);
+SELECT 'Convert Lrs geometry' AS 'FunctionInfo'
+ ,[dbo].[LRS_ConvertToLrsGeom](@geom, @startMeasure, @endMeasure) AS 'Geometry'
+ ,[dbo].[LRS_ConvertToLrsGeom](@geom, @startMeasure, @endMeasure).ToString() AS 'Geometry in String';
+
+-- 14. Reset Measure
+SET @geom = GEOMETRY::STGeomFromText('LINESTRING (1 1 0 10, 5 5 0 25)', @srid);
+
+SELECT 'Reset Measure' AS 'FunctionInfo'
+ ,[dbo].[LRS_ResetMeasure](@geom) AS 'Geometry'
+ ,@geom.ToString() AS 'Input Line'
+ ,[dbo].[LRS_ResetMeasure](@geom).ToString() AS 'Geometry in String'
+
+-- 15. Reverse Line String
+SET @geom = GEOMETRY::STGeomFromText('LINESTRING (1 1 0 0, 5 5 0 0)', @srid);
+
+SELECT 'Reverse Linear Geometry' AS 'FunctionInfo'
+ ,[dbo].[LRS_ReverseLinearGeometry](@geom) AS 'Geometry'
+ ,@geom.ToString() AS 'Input Line'
+ ,[dbo].[LRS_ReverseLinearGeometry](@geom).ToString() AS 'Geometry in String'
+
+-- 16. Scale Geometry Measures
+SET @geom = GEOMETRY::STGeomFromText('LINESTRING (2 2 0 6, 2 4 0 7, 8 4 0 8)', @srid);
+SET @startMeasure = 10;
+SET @endMeasure = 40;
+SET @measure = 5;
+
+SELECT 'Scale Geometry Measures' AS 'FunctionInfo'
+ ,[dbo].[LRS_ScaleGeometrySegment](@geom, @startMeasure, @endMeasure, @measure) AS 'Geometry'
+ ,@geom.ToString() AS 'Input Line'
+ ,[dbo].[LRS_ScaleGeometrySegment](@geom, @startMeasure, @endMeasure, @measure).ToString() AS 'Geometry in String'
+
+-- 17. Split Geometry Segment
+SET @geom = GEOMETRY::STGeomFromText('LINESTRING (10 1 NULL 10, 25 1 NULL 25)', @srid);
+SET @measure = 15;
+EXECUTE [dbo].[LRS_SplitGeometrySegment]
+ @geom
+ ,@measure
+ ,@geom1 OUTPUT
+ ,@geom2 OUTPUT
+
+SELECT 'Split Line Segment' AS 'FunctionInfo'
+ ,@geom.ToString() AS 'Input Line'
+ ,@geom1.ToString() AS 'Line Segment 1'
+ ,@geom2.ToString() AS 'Line Segment 2'
+
+-- 18. Translate Linear Geometry Measure
+SET @geom = GEOMETRY::STGeomFromText('LINESTRING (1 1 0 10, 5 5 0 20)', @srid);
+SET @measure = 5;
+
+SELECT 'Translate Measures' AS 'FunctionInfo'
+ ,[dbo].[LRS_TranslateMeasure](@geom, @measure) AS 'Geometry'
+ ,@geom.ToString() AS 'Input Line'
+ ,[dbo].[LRS_TranslateMeasure](@geom, @measure).ToString() AS 'Geometry in String'
+
+-- 19. Validate LRS Segment
+SET @geom1 = GEOMETRY::STGeomFromText('LINESTRING (1 1 0 0, 5 5 0 0)', @srid);
+SET @geom2 = GEOMETRY::STGeomFromText('LINESTRING (2 2 0, 2 4 2, 8 4 8, 12 4 12, 12 10 29, 8 10 22, 5 14 27)', @srid);
+SET @geom = GEOMETRY::STGeomFromText('LINESTRING (2 2, 2 4, 8 4)', @srid);
+
+SELECT 'Validate LRS Segment' AS 'FunctionInfo'
+ ,@geom1.ToString() AS 'Input Geom Segment'
+ ,[dbo].[LRS_ValidateLRSGeometry](@geom1) AS 'Valid State'
+UNION
+SELECT 'Validate LRS Segment' AS 'FunctionInfo'
+ ,@geom2.ToString() AS 'Input Geom Segment'
+ ,[dbo].[LRS_ValidateLRSGeometry](@geom2) AS 'Valid State'
+UNION
+SELECT 'Validate LRS Segment' AS 'FunctionInfo'
+ ,@geom.ToString() AS 'Input Geom Segment'
+ ,[dbo].[LRS_ValidateLRSGeometry](@geom) AS 'Valid State';
diff --git a/SQL Scripts/makevalid_example.sql b/src/SQL Scripts/makevalid_example.sql
similarity index 62%
rename from SQL Scripts/makevalid_example.sql
rename to src/SQL Scripts/makevalid_example.sql
index cf8b267..4af377a 100644
--- a/SQL Scripts/makevalid_example.sql
+++ b/src/SQL Scripts/makevalid_example.sql
@@ -1,8 +1,8 @@
--- Simple bow-tie polygon
-declare @geog varchar(max) = 'POLYGON ((0 0, 10 2, 10 0, 0 2, 0 0))';
-
-select dbo.IsValidGeographyFromText(@geog2, 4326);
--- returns 0 without throwing an exception
-
-select dbo.MakeValidGeographyFromText(@geog2, 4326);
+-- Simple bow-tie polygon
+declare @geog varchar(max) = 'POLYGON ((0 0, 10 2, 10 0, 0 2, 0 0))';
+
+select dbo.IsValidGeographyFromText(@geog, 4326);
+-- returns 0 without throwing an exception
+
+select dbo.MakeValidGeographyFromText(@geog, 4326);
-- returns multipolygon of two triangles
\ No newline at end of file
diff --git a/SQL Scripts/projection_example.sql b/src/SQL Scripts/projection_example.sql
similarity index 98%
rename from SQL Scripts/projection_example.sql
rename to src/SQL Scripts/projection_example.sql
index 417c6bc..bbf7f6c 100644
--- a/SQL Scripts/projection_example.sql
+++ b/src/SQL Scripts/projection_example.sql
@@ -1,7 +1,7 @@
--- Project point and linestring using Albers Equal Area projection
-declare @albers Projection
-set @albers = Projection::AlbersEqualArea(0, 0, 0, 60)
-
-select @albers.Project('POINT (45 30)').ToString()
-select @albers.Unproject(@albers.Project('LINESTRING (10 0, 10 10)')).ToString()
+-- Project point and linestring using Albers Equal Area projection
+declare @albers Projection
+set @albers = Projection::AlbersEqualArea(0, 0, 0, 60)
+
+select @albers.Project('POINT (45 30)').ToString()
+select @albers.Unproject(@albers.Project('LINESTRING (10 0, 10 10)')).ToString()
select @albers.ToString()
\ No newline at end of file
diff --git a/SQL Scripts/transform_example.sql b/src/SQL Scripts/transform_example.sql
similarity index 98%
rename from SQL Scripts/transform_example.sql
rename to src/SQL Scripts/transform_example.sql
index 626c122..ebc5445 100644
--- a/SQL Scripts/transform_example.sql
+++ b/src/SQL Scripts/transform_example.sql
@@ -1,11 +1,11 @@
--- Rotate line by 30 degrees counter-clockwise around (0 0)
-select AffineTransform::Rotate(45).Apply('LINESTRING (5 0, 10 0)').ToString()
--- Returns: LINESTRING (3.5355339059327378 3.5355339059327373, 7.0710678118654755 7.0710678118654746)
-
--- Decrease line size 5 times
-select AffineTransform::Scale(0.2, 0.2).Apply('LINESTRING (5 0, 10 0)').ToString()
--- Returns: LINESTRING (1 0, 2 0)
-
--- Move line down by 2 units
-select AffineTransform::Translate(0, -2).Apply('LINESTRING (5 0, 10 0)').ToString()
+-- Rotate line by 30 degrees counter-clockwise around (0 0)
+select AffineTransform::Rotate(45).Apply('LINESTRING (5 0, 10 0)').ToString()
+-- Returns: LINESTRING (3.5355339059327378 3.5355339059327373, 7.0710678118654755 7.0710678118654746)
+
+-- Decrease line size 5 times
+select AffineTransform::Scale(0.2, 0.2).Apply('LINESTRING (5 0, 10 0)').ToString()
+-- Returns: LINESTRING (1 0, 2 0)
+
+-- Move line down by 2 units
+select AffineTransform::Translate(0, -2).Apply('LINESTRING (5 0, 10 0)').ToString()
-- Returns: LINESTRING (5 -2, 10 -2)
\ No newline at end of file
diff --git a/src/SQL Scripts/util_geometry_example.sql b/src/SQL Scripts/util_geometry_example.sql
new file mode 100644
index 0000000..c2790b4
--- /dev/null
+++ b/src/SQL Scripts/util_geometry_example.sql
@@ -0,0 +1,36 @@
+-- Script that shows examples on exposed Util Functions in SQLSpatialTools Library
+-- Util Geometric Functions
+DECLARE @geom geometry;
+DECLARE @geom1 geometry
+DECLARE @geom2 geometry
+DECLARE @srid INT = 4326;
+
+-- 1. Utility Function - Polygon To Line
+SET @geom = GEOMETRY::STGeomFromText('POLYGON((-5 -5, -5 5, 5 5, 5 -5, -5 -5), (0 0, 3 0, 3 3, 0 3, 0 0))', @srid);
+SET @geom1 = GEOMETRY::STGeomFromText('CURVEPOLYGON((-122.3 47, 122.3 -47, 125.7 -49, 121 -38, -122.3 47)) ', @srid);
+SET @geom2 = GEOMETRY::STGeomFromText('MULTIPOLYGON(((1 1, 1 1, 1 1, 1 1)),((1 1, 3 1, 3 3, 1 3, 1 1)))', @srid);
+
+SELECT 'Polygon To Line' AS 'FunctionInfo'
+ ,@geom.ToString() AS 'Input Geom Segment'
+ ,[dbo].[Util_PolygonToLine](@geom).ToString() AS 'Converted Line'
+UNION ALL
+SELECT 'Polygon To Line' AS 'FunctionInfo'
+ ,@geom1.ToString() AS 'Input Geom Segment'
+ ,[dbo].[Util_PolygonToLine](@geom1).ToString() AS 'Converted Line'
+UNION ALL
+SELECT 'Polygon To Line' AS 'FunctionInfo'
+ ,@geom2.ToString() AS 'Input Geom Segment'
+ ,[dbo].[Util_PolygonToLine](@geom2).ToString() AS 'Converted Line';
+
+-- 2. Utility Function - Extract
+SELECT 'Extract' AS 'FunctionInfo'
+ ,@geom.ToString() AS 'Input Geom Segment'
+ ,[dbo].[Util_Extract](@geom, 1, 2).ToString() AS 'Extracted Geom'
+ UNION ALL
+SELECT 'Extract' AS 'FunctionInfo'
+ ,@geom1.ToString() AS 'Input Geom Segment'
+ ,[dbo].[Util_Extract](@geom, 1, 1).ToString() AS 'Extracted Geom'
+UNION ALL
+SELECT 'Extract' AS 'FunctionInfo'
+ ,@geom2.ToString() AS 'Input Geom Segment'
+ ,[dbo].[Util_Extract](@geom, 1, 2).ToString() AS 'Extracted Geom';
\ No newline at end of file
diff --git a/SQLSpatialTools.csproj b/src/SQLSpatialTools.csproj
similarity index 57%
rename from SQLSpatialTools.csproj
rename to src/SQLSpatialTools.csproj
index 0ea0e38..1d9f1c5 100644
--- a/SQLSpatialTools.csproj
+++ b/src/SQLSpatialTools.csproj
@@ -1,188 +1,240 @@
-
-
-
- Debug
- AnyCPU
- 9.0.30729
- 2.0
- {09428C16-7DAE-4C28-A853-E4F04DD0BEDD}
- Library
- Properties
- SQLSpatialTools
- SQLSpatialTools
- v3.5
- 512
-
-
- 4.0
-
-
- publish\
- true
- Disk
- false
- Foreground
- 7
- Days
- false
- false
- true
- 0
- 1.0.0.%2a
- false
- false
- true
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
- AllRules.ruleset
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
- AllRules.ruleset
-
-
-
- False
- .\Microsoft.SqlServer.Types.dll
-
-
-
- 3.5
-
-
- 3.5
-
-
- 3.5
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
-
-
-
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
-
-
- False
- .NET Framework 3.5 SP1 Client Profile
- false
-
-
- False
- .NET Framework 3.5 SP1
- true
-
-
- False
- Windows Installer 3.1
- true
-
-
-
-
+
+
+
+ 10.0
+
+
+ Debug
+ AnyCPU
+ 9.0.30729
+ 2.0
+ {09428C16-7DAE-4C28-A853-E4F04DD0BEDD}
+ Library
+ Properties
+ SQLSpatialTools
+ SQLSpatialTools
+ v3.5
+ 512
+
+
+ 4.0
+
+
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ false
+ true
+
+
+ true
+ full
+ false
+ ..\output\lib
+ ..\output\intermediate\lib
+ DEBUG;TRACE
+ prompt
+ 4
+ AllRules.ruleset
+
+
+ full
+ true
+ ..\output\lib
+ ..\output\intermediate\lib
+ TRACE
+ prompt
+ 4
+ AllRules.ruleset
+ true
+
+
+ true
+
+
+ ..\SpatialTools.pfx
+
+
+
+ False
+ ..\packages\Microsoft.SqlServer.Types.14.0.1016.290\lib\net40\Microsoft.SqlServer.Types.dll
+
+
+
+ 3.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+ False
+ .NET Framework 3.5 SP1 Client Profile
+ false
+
+
+ False
+ .NET Framework 3.5 SP1
+ true
+
+
+ False
+ Windows Installer 3.1
+ true
+
+
+
+
+ PreserveNewest
+
+
+
+
+ Always
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. %0AUse NuGet Package Restore to download them. %0AFor more information, see http://go.microsoft.com/fwlink/?LinkID=322105. %0AThe missing file is {0}.
+
+
+
+
+ if exist "$(ProjectDir)obj" rd "$(ProjectDir)obj" /S /Q
+powershell.exe -ExecutionPolicy Unrestricted [io.file]::WriteAllText('$(TargetDir)SQL Scripts\Register.sql', ((gc '$(TargetDir)SQL Scripts\Register.sql') -replace 'DLLPath', '$(TargetPath)' -join \"`r`n\"))
+powershell.exe -ExecutionPolicy Unrestricted [io.file]::WriteAllText('$(TargetDir)SQL Scripts\RegisterAndValidate.sql', ((gc '$(TargetDir)SQL Scripts\RegisterAndValidate.sql') -replace 'ScriptDirPath', '$(TargetDir)SQL Scripts' -join \"`r`n\"))
+exit 0
+
+
\ No newline at end of file
diff --git a/Sinks/DensifyGeographySink.cs b/src/Sinks/Geography/DensifyGeographySink.cs
similarity index 59%
rename from Sinks/DensifyGeographySink.cs
rename to src/Sinks/Geography/DensifyGeographySink.cs
index d2219c3..26a6890 100644
--- a/Sinks/DensifyGeographySink.cs
+++ b/src/Sinks/Geography/DensifyGeographySink.cs
@@ -1,122 +1,126 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-
-using System;
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- ///
- /// This class is used for generating a new geography object where additional points are inserted
- /// along every line in such a way that the angle between two consecutive points does not
- /// exceed a prescribed angle. The points are generated between the unit vectors that correspond
- /// to the line's start and end along the great-circle arc on the unit sphere . This follows the
- /// definition of geodetic lines in SQL Server.
- ///
- public class DensifyGeographySink : IGeographySink
- {
- // Minimum angle. If the user specifies a smaller angle, the angle will be set to this minimum.
- public static readonly double MinAngle = 0.000001;
-
- // Maximum angular difference in degrees between two consecutive points in the "densified" line.
- private readonly double _angle;
-
- // Previous point added.
- private Vector3 _startPoint;
-
- private readonly IGeographySink _sink;
-
- // Constructor.
- public DensifyGeographySink(IGeographySink sink, double angle)
- {
- if (sink == null)
- throw new ArgumentNullException("sink");
- _sink = sink;
-
- if (angle < MinAngle)
- _angle = MinAngle;
- else
- _angle = angle;
- }
-
- #region IGeographySink Members
-
- public void AddLine(double latitude, double longitude, double? z, double? m)
- {
- // Transforming from geodetic coordinates to a unit vector.
- Vector3 endPoint = Util.SphericalDegToCartesian(latitude, longitude);
-
- double angle = endPoint.Angle(_startPoint);
- if (angle > MinAngle)
- {
- // _startPoint and endPoint are the unit vectors that correspond to the input
- // start and end points. In their 3D space we operate in a local coordinate system
- // where _startPoint is the x axis and the xy plane contains endPoint. Every
- // point is now generated from the previous one by a fixed rotation in the local
- // xy plane, and converted back to geodetic coordinates.
-
- // Construct the local z and y axes.
- Vector3 zAxis = (_startPoint + endPoint).CrossProduct(_startPoint - endPoint).Unitize();
- Vector3 yAxis = (_startPoint).CrossProduct(zAxis);
-
- // Calculating how many points we need.
- int count = Convert.ToInt32(Math.Ceiling(angle / Util.ToRadians(_angle)));
-
- // Scaling the angle so that points are equaly placed.
- double exactAngle = angle / count;
-
- double cosine = Math.Cos(exactAngle);
- double sine = Math.Sin(exactAngle);
-
- // Setting the first x and y points in our local coordinate system.
- double x = cosine;
- double y = sine;
-
- for (int i = 0; i < count - 1; i++)
- {
- Vector3 newPoint = (_startPoint * x + yAxis * y).Unitize();
-
- // Adding the point.
- _sink.AddLine(Util.LatitudeDeg(newPoint), Util.LongitudeDeg(newPoint), null, null);
-
- // Rotating to get next point.
- double r = x * cosine - y * sine;
- y = x * sine + y * cosine;
- x = r;
- }
- }
- _sink.AddLine(latitude, longitude, z, m);
-
- // Remembering last point we added.
- _startPoint = endPoint;
- }
-
- public void BeginFigure(double latitude, double longitude, double? z, double? m)
- {
- // Starting the figure, remembering the vector that corresponds to the first point.
- _startPoint = Util.SphericalDegToCartesian(latitude, longitude);
- _sink.BeginFigure(latitude, longitude, z, m);
- }
-
- public void BeginGeography(OpenGisGeographyType type)
- {
- _sink.BeginGeography(type);
- }
-
- public void EndFigure()
- {
- _sink.EndFigure();
- }
-
- public void EndGeography()
- {
- _sink.EndGeography();
- }
-
- public void SetSrid(int srid)
- {
- _sink.SetSrid(srid);
- }
-
- #endregion
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Sinks.Geography
+{
+ ///
+ /// This class is used for generating a new geography object where additional points are inserted
+ /// along every line in such a way that the angle between two consecutive points does not
+ /// exceed a prescribed angle. The points are generated between the unit vectors that correspond
+ /// to the line's start and end along the great-circle arc on the unit sphere . This follows the
+ /// definition of geodetic lines in SQL Server.
+ ///
+ public class DensifyGeographySink : IGeographySink110
+ {
+ // Minimum angle. If the user specifies a smaller angle, the angle will be set to this minimum.
+ private const double MinAngle = 0.000001;
+
+ // Maximum angular difference in degrees between two consecutive points in the "densified" line.
+ private readonly double _angle;
+
+ // Previous point added.
+ private Vector3 _startPoint;
+
+ private readonly IGeographySink110 _sink;
+
+ // Constructor.
+ public DensifyGeographySink(IGeographySink110 sink, double angle)
+ {
+ _sink = sink ?? throw new ArgumentNullException(nameof(sink), "sink");
+
+ _angle = angle < MinAngle ? MinAngle : angle;
+ }
+
+ #region IGeographySink Members
+
+ public void AddLine(double latitude, double longitude, double? z, double? m)
+ {
+ // Transforming from geodetic coordinates to a unit vector.
+ var endPoint = SpatialUtil.SphericalDegToCartesian(latitude, longitude);
+
+ var angle = endPoint.Angle(_startPoint);
+ if (angle > MinAngle)
+ {
+ // _startPoint and endPoint are the unit vectors that correspond to the input
+ // start and end points. In their 3D space we operate in a local coordinate system
+ // where _startPoint is the x axis and the xy plane contains endPoint. Every
+ // point is now generated from the previous one by a fixed rotation in the local
+ // xy plane, and converted back to geodetic coordinates.
+
+ // Construct the local z and y axes.
+ var zAxis = (_startPoint + endPoint).CrossProduct(_startPoint - endPoint).Unitize();
+ var yAxis = (_startPoint).CrossProduct(zAxis);
+
+ // Calculating how many points we need.
+ var count = Convert.ToInt32(Math.Ceiling(angle / SpatialUtil.ToRadians(_angle)));
+
+ // Scaling the angle so that points are equally placed.
+ var exactAngle = angle / count;
+
+ var cosine = Math.Cos(exactAngle);
+ var sine = Math.Sin(exactAngle);
+
+ // Setting the first x and y points in our local coordinate system.
+ var x = cosine;
+ var y = sine;
+
+ for (var i = 0; i < count - 1; i++)
+ {
+ var newPoint = (_startPoint * x + yAxis * y).Unitize();
+
+ // Adding the point.
+ _sink.AddLine(SpatialUtil.LatitudeDeg(newPoint), SpatialUtil.LongitudeDeg(newPoint), null, null);
+
+ // Rotating to get next point.
+ var r = x * cosine - y * sine;
+ y = x * sine + y * cosine;
+ x = r;
+ }
+ }
+ _sink.AddLine(latitude, longitude, z, m);
+
+ // Remembering last point we added.
+ _startPoint = endPoint;
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void BeginFigure(double latitude, double longitude, double? z, double? m)
+ {
+ // Starting the figure, remembering the vector that corresponds to the first point.
+ _startPoint = SpatialUtil.SphericalDegToCartesian(latitude, longitude);
+ _sink.BeginFigure(latitude, longitude, z, m);
+ }
+
+ public void BeginGeography(OpenGisGeographyType type)
+ {
+ _sink.BeginGeography(type);
+ }
+
+ public void EndFigure()
+ {
+ _sink.EndFigure();
+ }
+
+ public void EndGeography()
+ {
+ _sink.EndGeography();
+ }
+
+ public void SetSrid(int srid)
+ {
+ _sink.SetSrid(srid);
+ }
+
+ #endregion
+ }
}
\ No newline at end of file
diff --git a/src/Sinks/Geography/GeographyFilters.cs b/src/Sinks/Geography/GeographyFilters.cs
new file mode 100644
index 0000000..d3c6614
--- /dev/null
+++ b/src/Sinks/Geography/GeographyFilters.cs
@@ -0,0 +1,367 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types;
+
+namespace SQLSpatialTools.Sinks.Geography
+{
+ public class GeographyEmptyShapeFilter : IGeographySink110
+ {
+ private readonly IGeographySink110 _sink;
+ private readonly Queue _types = new Queue();
+ private bool _root = true;
+
+ public GeographyEmptyShapeFilter(IGeographySink110 sink)
+ {
+ _sink = sink;
+ }
+
+ public void SetSrid(int srid)
+ {
+ _sink.SetSrid(srid);
+ }
+
+ public void BeginGeography(OpenGisGeographyType type)
+ {
+ if (_root)
+ {
+ _root = false;
+ _sink.BeginGeography(type);
+ }
+ else
+ {
+ _types.Enqueue(type);
+ }
+ }
+
+ public void EndGeography()
+ {
+ if (_types.Count > 0)
+ _types.Dequeue();
+ else
+ _sink.EndGeography();
+ }
+
+ public void BeginFigure(double latitude, double longitude, double? z, double? m)
+ {
+ while (_types.Count > 0)
+ _sink.BeginGeography(_types.Dequeue());
+ _sink.BeginFigure(latitude, longitude, z, m);
+ }
+
+ public void AddLine(double latitude, double longitude, double? z, double? m)
+ {
+ _sink.AddLine(latitude, longitude, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ _sink.EndFigure();
+ }
+ }
+
+ public class GeographyPointFilter : IGeographySink110
+ {
+ private readonly IGeographySink110 _sink;
+ private int _depth;
+ private bool _root = true;
+
+ public GeographyPointFilter(IGeographySink110 sink)
+ {
+ _sink = sink;
+ }
+
+ public void SetSrid(int srid)
+ {
+ _sink.SetSrid(srid);
+ }
+
+ public void BeginGeography(OpenGisGeographyType type)
+ {
+ if (type == OpenGisGeographyType.Point || type == OpenGisGeographyType.MultiPoint)
+ {
+ if (_root)
+ {
+ _root = false;
+ _sink.BeginGeography(OpenGisGeographyType.GeometryCollection);
+ _sink.EndGeography();
+ }
+ _depth++;
+ }
+ else
+ {
+ _root = false;
+ _sink.BeginGeography(type);
+ }
+ }
+
+ public void EndGeography()
+ {
+ if (_depth > 0)
+ _depth--;
+ else
+ _sink.EndGeography();
+ }
+
+ public void BeginFigure(double latitude, double longitude, double? z, double? m)
+ {
+ if (_depth == 0)
+ _sink.BeginFigure(latitude, longitude, z, m);
+ }
+
+ public void AddLine(double latitude, double longitude, double? z, double? m)
+ {
+ if (_depth == 0)
+ _sink.AddLine(latitude, longitude, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ if (_depth == 0)
+ _sink.EndFigure();
+ }
+ }
+
+ public class GeographyShortLineStringFilter : IGeographySink110
+ {
+ private readonly IGeographySink110 _sink;
+ private readonly double _tolerance;
+ private int _srid;
+ private bool _insideLineString;
+ private readonly List _figure = new List();
+
+ public GeographyShortLineStringFilter(IGeographySink110 sink, double tolerance)
+ {
+ _sink = sink;
+ _tolerance = tolerance;
+ }
+
+ public void SetSrid(int srid)
+ {
+ _srid = srid;
+ _sink.SetSrid(srid);
+ }
+
+ public void BeginGeography(OpenGisGeographyType type)
+ {
+ _sink.BeginGeography(type);
+ _insideLineString = type == OpenGisGeographyType.LineString;
+ }
+
+ public void EndGeography()
+ {
+ _sink.EndGeography();
+ }
+
+ public void BeginFigure(double latitude, double longitude, double? z, double? m)
+ {
+ if (_insideLineString)
+ {
+ _figure.Clear();
+ _figure.Add(new Vertex(latitude, longitude, z, m));
+ }
+ else
+ {
+ _sink.BeginFigure(latitude, longitude, z, m);
+ }
+ }
+
+ public void AddLine(double latitude, double longitude, double? z, double? m)
+ {
+ if (_insideLineString)
+ {
+ _figure.Add(new Vertex(latitude, longitude, z, m));
+ }
+ else
+ {
+ _sink.AddLine(latitude, longitude, z, m);
+ }
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ if (_insideLineString)
+ {
+ if (!IsShortLineString())
+ {
+ PopulateFigure(_sink);
+ }
+ }
+ else
+ {
+ _sink.EndFigure();
+ }
+ }
+
+ private bool IsShortLineString()
+ {
+ try
+ {
+ SqlGeographyBuilder b = new SqlGeographyBuilder();
+ b.SetSrid(_srid);
+ b.BeginGeography(OpenGisGeographyType.LineString);
+ PopulateFigure(b);
+ b.EndGeography();
+ SqlGeography g = b.ConstructedGeography;
+ return g.STLength().Value < _tolerance;
+ }
+ catch (FormatException) { }
+ catch (ArgumentException) { }
+ return false;
+ }
+
+ private void PopulateFigure(IGeographySink110 sink)
+ {
+ _figure[0].BeginFigure(sink);
+ for (int i = 1; i < _figure.Count; i++)
+ _figure[i].AddLine(sink);
+ sink.EndFigure();
+ }
+ }
+
+ public class GeographyThinRingFilter : IGeographySink110
+ {
+ private readonly IGeographySink110 _sink;
+ private readonly double _tolerance;
+ private bool _insidePolygon;
+ private int _srid;
+ private readonly List _figure = new List();
+
+ public GeographyThinRingFilter(IGeographySink110 sink, double tolerance)
+ {
+ _sink = sink;
+ _tolerance = tolerance;
+ }
+
+ public void SetSrid(int srid)
+ {
+ _srid = srid;
+ _sink.SetSrid(srid);
+ }
+
+ public void BeginGeography(OpenGisGeographyType type)
+ {
+ _sink.BeginGeography(type);
+ _insidePolygon = type == OpenGisGeographyType.Polygon;
+ }
+
+ public void EndGeography()
+ {
+ _sink.EndGeography();
+ }
+
+ public void BeginFigure(double latitude, double longitude, double? z, double? m)
+ {
+ if (_insidePolygon)
+ {
+ _figure.Clear();
+ _figure.Add(new Vertex(latitude, longitude, z, m));
+ }
+ else
+ {
+ _sink.BeginFigure(latitude, longitude, z, m);
+ }
+ }
+
+ public void AddLine(double latitude, double longitude, double? z, double? m)
+ {
+ if (_insidePolygon)
+ {
+ _figure.Add(new Vertex(latitude, longitude, z, m));
+ }
+ else
+ {
+ _sink.AddLine(latitude, longitude, z, m);
+ }
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ if (_insidePolygon)
+ {
+ if (!IsThinRing())
+ {
+ PopulateFigure(_sink, false);
+ }
+ }
+ else
+ {
+ _sink.EndFigure();
+ }
+ }
+
+ private bool IsThinRing()
+ {
+ SqlGeography poly = RingToPolygon(true);
+ if (poly == null)
+ {
+ // ring was not valid, try with different orientation
+ poly = RingToPolygon(false);
+ if (poly == null)
+ {
+ // if both orientations are invalid, we are dealing with very thin ring
+ // so just return true
+ return true;
+ }
+ }
+ return poly.STArea().Value < _tolerance * poly.STLength().Value;
+ }
+
+ private SqlGeography RingToPolygon(bool reverse)
+ {
+ try
+ {
+ SqlGeographyBuilder b = new SqlGeographyBuilder();
+ b.SetSrid(_srid);
+ b.BeginGeography(OpenGisGeographyType.Polygon);
+ PopulateFigure(b, reverse);
+ b.EndGeography();
+ return b.ConstructedGeography;
+ }
+ catch (FormatException) { }
+ catch (ArgumentException) { }
+ return null;
+ }
+
+ private void PopulateFigure(IGeographySink110 sink, bool reverse)
+ {
+ if (reverse)
+ {
+ _figure[_figure.Count - 1].BeginFigure(sink);
+ for (int i = _figure.Count - 2; i >= 0; i--)
+ _figure[i].AddLine(sink);
+ }
+ else
+ {
+ _figure[0].BeginFigure(sink);
+ for (int i = 1; i < _figure.Count; i++)
+ _figure[i].AddLine(sink);
+ }
+ sink.EndFigure();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Sinks/LocateAlongGeographySink.cs b/src/Sinks/Geography/LocateAlongGeographySink.cs
similarity index 65%
rename from Sinks/LocateAlongGeographySink.cs
rename to src/Sinks/Geography/LocateAlongGeographySink.cs
index d7aba14..2b9b4a1 100644
--- a/Sinks/LocateAlongGeographySink.cs
+++ b/src/Sinks/Geography/LocateAlongGeographySink.cs
@@ -1,101 +1,107 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-
-using Microsoft.SqlServer.Types;
-using System;
-
-namespace SQLSpatialTools
-{
- /**
- * This class implements a geography sink that finds a point along a geography linestring instance and pipes
- * it to another sink.
- */
- class LocateAlongGeographySink : IGeographySink
- {
-
- double _distance; // The running count of how much further we have to go.
- SqlGeography _lastPoint; // The last point in the LineString we have passed.
- SqlGeography _foundPoint; // This is the point we're looking for, assuming it isn't null, we're done.
- int _srid; // The _srid we are working in.
- SqlGeographyBuilder _target; // Where we place our result.
-
- // We target another builder, to which we will send a point representing the point we find.
- // We also take a distance, which is the point along the input linestring we will travel.
- // Note that we only operate on LineString instances: anything else will throw an exception.
- public LocateAlongGeographySink(double distance, SqlGeographyBuilder target)
- {
- _target = target;
- _distance = distance;
- }
-
- // Save the SRID for later
- public void SetSrid(int srid)
- {
- _srid = srid;
- }
-
- // Start the geography. Throw if it isn't a LineString.
- public void BeginGeography(OpenGisGeographyType type)
- {
- if (type != OpenGisGeographyType.LineString)
- throw new ArgumentException("This operation may only be executed on LineString instances.");
- }
-
- // Start the figure. Note that since we only operate on LineStrings, this should only be executed
- // once.
- public void BeginFigure(double latitude, double longitude, double? z, double? m)
- {
- // Memorize the point.
- _lastPoint = SqlGeography.Point(latitude, longitude, _srid);
- }
-
- // This is where the real work is done.
- public void AddLine(double latitude, double longitude, double? z, double? m)
- {
- // If we've already found a point, then we're done. We just need to keep ignoring these
- // pesky calls.
- if (_foundPoint != null) return;
-
- // Make a point for our current position.
- SqlGeography thisPoint = SqlGeography.Point(latitude, longitude, _srid);
-
- // is the found point between this point and the last, or past this point?
- double length = thisPoint.STDistance(_lastPoint).Value;
- if (length < _distance)
- {
- // it's past this point---just step along the line
- _distance -= length;
- _lastPoint = thisPoint;
- }
- else
- {
- // now we need to do the hard work and find the point in between these two
- _foundPoint = Functions.InterpolateBetweenGeog(_lastPoint, thisPoint, _distance);
- }
- }
-
- // This is a NOP.
- public void EndFigure()
- {
- }
-
- // When we end, we'll make all of our output calls to our target.
- // Here's also where we catch whether we've run off the end of our LineString.
- public void EndGeography()
- {
- if (_foundPoint != null)
- {
- // We could use a simple point constructor, but by targetting another sink we can use this
- // class in a pipeline.
- _target.SetSrid(_srid);
- _target.BeginGeography(OpenGisGeographyType.Point);
- _target.BeginFigure(_foundPoint.Lat.Value, _foundPoint.Long.Value);
- _target.EndFigure();
- _target.EndGeography();
- }
- else
- {
- throw new ArgumentException("Distance provided is greated then the length of the LineString.");
- }
- }
- }
-}
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.Sinks.Geography
+{
+ ///
+ /// This class implements a geography sink that finds a point along a geography linestring instance and pipes
+ /// it to another sink.
+ ///
+ internal class LocateAlongGeographySink : IGeographySink110
+ {
+ private double _distance; // The running count of how much further we have to go.
+ private SqlGeography _lastPoint; // The last point in the LineString we have passed.
+ private SqlGeography _foundPoint; // This is the point we're looking for, assuming it isn't null, we're done.
+ private int _srid; // The _srid we are working in.
+ private readonly SqlGeographyBuilder _target; // Where we place our result.
+
+ // We target another builder, to which we will send a point representing the point we find.
+ // We also take a distance, which is the point along the input linestring we will travel.
+ // Note that we only operate on LineString instances: anything else will throw an exception.
+ public LocateAlongGeographySink(double distance, SqlGeographyBuilder target)
+ {
+ _target = target;
+ _distance = distance;
+ }
+
+ // Save the SRID for later
+ public void SetSrid(int srid)
+ {
+ _srid = srid;
+ }
+
+ // Start the geography. Throw if it isn't a LineString.
+ public void BeginGeography(OpenGisGeographyType type)
+ {
+ if (type != OpenGisGeographyType.LineString)
+ throw new ArgumentException("This operation may only be executed on LineString instances.");
+ }
+
+ // Start the figure. Note that since we only operate on LineStrings, this should only be executed
+ // once.
+ public void BeginFigure(double latitude, double longitude, double? z, double? m)
+ {
+ // Memorize the point.
+ _lastPoint = SqlGeography.Point(latitude, longitude, _srid);
+ }
+
+ // This is where the real work is done.
+ public void AddLine(double latitude, double longitude, double? z, double? m)
+ {
+ // If we've already found a point, then we're done. We just need to keep ignoring these
+ // pesky calls.
+ if (_foundPoint != null) return;
+
+ // Make a point for our current position.
+ var thisPoint = SqlGeography.Point(latitude, longitude, _srid);
+
+ // is the found point between this point and the last, or past this point?
+ var length = thisPoint.STDistance(_lastPoint).Value;
+ if (length < _distance)
+ {
+ // it's past this point---just step along the line
+ _distance -= length;
+ _lastPoint = thisPoint;
+ }
+ else
+ {
+ // now we need to do the hard work and find the point in between these two
+ _foundPoint = Functions.General.Geography.InterpolateBetweenGeog(_lastPoint, thisPoint, _distance);
+ }
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ // This is a NOP.
+ public void EndFigure()
+ {
+ }
+
+ // When we end, we'll make all of our output calls to our target.
+ // Here's also where we catch whether we've run off the end of our LineString.
+ public void EndGeography()
+ {
+ if (_foundPoint != null)
+ {
+ // We could use a simple point constructor, but by targeting another sink we can use this
+ // class in a pipeline.
+ _target.SetSrid(_srid);
+ _target.BeginGeography(OpenGisGeographyType.Point);
+ _target.BeginFigure(_foundPoint.Lat.Value, _foundPoint.Long.Value);
+ _target.EndFigure();
+ _target.EndGeography();
+ }
+ else
+ {
+ throw new ArgumentException("Distance provided is greater then the length of the LineString.");
+ }
+ }
+ }
+}
diff --git a/src/Sinks/Geography/Projector.cs b/src/Sinks/Geography/Projector.cs
new file mode 100644
index 0000000..87ae11f
--- /dev/null
+++ b/src/Sinks/Geography/Projector.cs
@@ -0,0 +1,62 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types.SQL;
+
+namespace SQLSpatialTools.Sinks.Geography
+{
+ ///
+ /// This class projects a geography segment based on specified project to a geometry segment.
+ ///
+ public sealed class Projector : IGeographySink110
+ {
+ private readonly SqlProjection _projection;
+ private readonly IGeometrySink110 _sink;
+
+ public Projector(SqlProjection projection, IGeometrySink110 sink)
+ {
+ _projection = projection;
+ _sink = sink;
+ }
+
+ public void BeginGeography(OpenGisGeographyType type)
+ {
+ _sink.BeginGeometry((OpenGisGeometryType)type);
+ }
+
+ public void EndGeography()
+ {
+ _sink.EndGeometry();
+ }
+
+ public void BeginFigure(double latitude, double longitude, double? z, double? m)
+ {
+ _projection.ProjectPoint(latitude, longitude, out var x, out var y);
+ _sink.BeginFigure(x, y, z, m);
+ }
+
+ public void AddLine(double latitude, double longitude, double? z, double? m)
+ {
+ _projection.ProjectPoint(latitude, longitude, out var x, out var y);
+ _sink.AddLine(x, y, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ _sink.EndFigure();
+ }
+
+ public void SetSrid(int srid)
+ {
+ _sink.SetSrid(srid);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Sinks/Geography/VacuousGeographyToGeometrySink.cs b/src/Sinks/Geography/VacuousGeographyToGeometrySink.cs
new file mode 100644
index 0000000..5a1b861
--- /dev/null
+++ b/src/Sinks/Geography/VacuousGeographyToGeometrySink.cs
@@ -0,0 +1,62 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.Sinks.Geography
+{
+ ///
+ /// This class implements a completely trivial conversion from geography to geometry, simply taking each
+ /// point(lat, long) --> (y, x). The class takes a target geometry sink, as well as the target SRID to
+ /// assign to the results.
+ ///
+ public class VacuousGeographyToGeometrySink : IGeographySink110
+ {
+ private readonly IGeometrySink110 _target;
+ private readonly int _targetSrid;
+
+ public VacuousGeographyToGeometrySink(int targetSrid, IGeometrySink110 target)
+ {
+ _target = target;
+ _targetSrid = targetSrid;
+ }
+
+ public void AddLine(double latitude, double longitude, double? z, double? m)
+ {
+ _target.AddLine(longitude, latitude, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void BeginFigure(double latitude, double longitude, double? z, double? m)
+ {
+ _target.BeginFigure(longitude, latitude, z, m);
+ }
+
+ public void BeginGeography(OpenGisGeographyType type)
+ {
+ // Convert geography to geometry types...
+ _target.BeginGeometry((OpenGisGeometryType) type);
+ }
+
+ public void EndFigure()
+ {
+ _target.EndFigure();
+ }
+
+ public void EndGeography()
+ {
+ _target.EndGeometry();
+ }
+
+ public void SetSrid(int srid)
+ {
+ _target.SetSrid(_targetSrid);
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/BuidLRSMultiLineSink.cs b/src/Sinks/Geometry/BuidLRSMultiLineSink.cs
new file mode 100644
index 0000000..b620fb5
--- /dev/null
+++ b/src/Sinks/Geometry/BuidLRSMultiLineSink.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Utility;
+using System;
+using System.Text;
+
+namespace SQLSpatialTools
+{
+ ///
+ /// This class implements a geometry sink that builds LRS multiline.
+ /// Second segment measure is updated with offset difference.
+ ///
+ class BuildLRSMultiLineSink : IGeometrySink110
+ {
+ private LRSLine CurrentLine;
+ public LRSMultiLine MultiLine;
+ private int Srid;
+
+ public void ScaleMeasure(double offsetMeasure)
+ {
+ MultiLine.ScaleMeasure(offsetMeasure);
+ }
+ public void TranslateMeasure(double offsetMeasure)
+ {
+ MultiLine.TranslateMeasure(offsetMeasure);
+ }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BuildLRSMultiLineSink()
+ {
+ MultiLine = new LRSMultiLine();
+ }
+
+ public void SetSrid(int srid)
+ {
+ Srid = srid;
+ }
+
+ // Start the geometry.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (type == OpenGisGeometryType.LineString)
+ {
+ CurrentLine = new LRSLine();
+ CurrentLine.SetSrid(Srid);
+ }
+ }
+
+ // Start the figure.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ CurrentLine.AddPoint(new LRSPoint(x, y, z, m));
+ }
+
+ // This is where the real work is done.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ CurrentLine.AddPoint(new LRSPoint(x, y, z, m));
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ // This is a NO-OP
+ public void EndFigure()
+ {
+ MultiLine.AddLine(CurrentLine);
+ }
+
+ // This is a NO-OP
+ public void EndGeometry()
+ {
+ }
+ public SqlGeometry ToSqlGeometry()
+ {
+ return MultiLine.ToSqlGeometry();
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/BuildLRSMultiLineSink.cs b/src/Sinks/Geometry/BuildLRSMultiLineSink.cs
new file mode 100644
index 0000000..5d80b1e
--- /dev/null
+++ b/src/Sinks/Geometry/BuildLRSMultiLineSink.cs
@@ -0,0 +1,73 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that builds LRS multiline.
+ /// Second segment measure is updated with offset difference.
+ ///
+ internal class BuildLRSMultiLineSink : IGeometrySink110
+ {
+ private int _srid;
+ private readonly bool _doUpdateM;
+ private readonly double _offsetM;
+ private LRSLine _currentLine;
+ public LRSMultiLine MultiLine;
+
+ public BuildLRSMultiLineSink(bool doUpdateM, double? offsetM)
+ {
+ _doUpdateM = doUpdateM;
+ _offsetM = offsetM ?? 0;
+ }
+
+ // Initialize MultiLine and sets srid.
+ public void SetSrid(int srid)
+ {
+ MultiLine = new LRSMultiLine(srid);
+ _srid = srid;
+ }
+
+ // Start the geometry.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (type == OpenGisGeometryType.LineString)
+ _currentLine = new LRSLine(_srid);
+ }
+
+ // Just add the points to the current line.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ var currentM = _doUpdateM ? m + _offsetM : m;
+ _currentLine.AddPoint(x, y, z, currentM);
+ }
+
+ // Just add the points to the current line.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ var currentM = _doUpdateM ? m + _offsetM : m;
+ _currentLine.AddPoint(x, y, z, currentM);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ // Add the current line to the MULTILINESTRING collection
+ public void EndFigure()
+ {
+ MultiLine.AddLine(_currentLine);
+ }
+
+ // This is a NO-OP
+ public void EndGeometry()
+ {
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/BuildMultiLineFromLinesSink.cs b/src/Sinks/Geometry/BuildMultiLineFromLinesSink.cs
new file mode 100644
index 0000000..0eac5e5
--- /dev/null
+++ b/src/Sinks/Geometry/BuildMultiLineFromLinesSink.cs
@@ -0,0 +1,84 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Utility;
+using System;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that builds Multiline from Line String
+ ///
+ internal class BuildMultiLineFromLinesSink : IGeometrySink110
+ {
+ private readonly SqlGeometryBuilder _target;
+ private bool _isFirstPoint;
+ private int _linesCount;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The target.
+ /// The lines count.
+ public BuildMultiLineFromLinesSink(SqlGeometryBuilder target, int linesCount)
+ {
+ _target = target;
+ _linesCount = linesCount;
+ _isFirstPoint = true;
+ }
+
+ public void SetSrid(int srid)
+ {
+ _target.SetSrid(srid);
+ }
+
+ // Start the geometry.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (type == OpenGisGeometryType.Point)
+ return;
+
+ if (type != OpenGisGeometryType.LineString)
+ SpatialExtensions.ThrowException(ErrorMessage.LineStringCompatible);
+
+ if (_isFirstPoint)
+ {
+ _isFirstPoint = false;
+ _target.BeginGeometry(OpenGisGeometryType.MultiLineString);
+ }
+ _target.BeginGeometry(type);
+ _linesCount--;
+ }
+
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _target.BeginFigure(x, y, z, m);
+ }
+
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _target.AddLine(x, y, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ _target.EndFigure();
+ }
+
+ public void EndGeometry()
+ {
+ _target.EndGeometry();
+
+ // end of multi line
+ if (_linesCount == 0)
+ _target.EndGeometry();
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/ClipMGeometrySegmentSink.cs b/src/Sinks/Geometry/ClipMGeometrySegmentSink.cs
new file mode 100644
index 0000000..c49d58c
--- /dev/null
+++ b/src/Sinks/Geometry/ClipMGeometrySegmentSink.cs
@@ -0,0 +1,272 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Utility;
+using System;
+using Ext = SQLSpatialTools.Utility.SpatialExtensions;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that clips a line segment based on measure.
+ ///
+ internal class ClipMGeometrySegmentSink : IGeometrySink110
+ {
+ // Where we place our result
+ private readonly SqlGeometryBuilder _target;
+ private readonly double _tolerance;
+ private readonly bool _retainClipMeasure;
+
+ private readonly double _clipStartMeasure;
+ private readonly double _clipEndMeasure;
+ private double _previousX;
+ private double _previousY;
+ private double _previousM;
+ private bool _started;
+ private bool _finished;
+
+ // The _srid we are working in.
+ private int _srid;
+
+ ///
+ /// We target another builder, to which we will send a point representing the point we find.
+ /// We also take a distance, which is the point along the input linestring we will travel.
+ /// Note that we only operate on LineString instances: anything else will throw an exception.
+ ///
+ /// Start Measure to be clipped
+ /// End Measure to be clipped
+ /// SqlGeometry builder
+ /// tolerance value
+ /// Flag to retain ClipMeasure values
+ public ClipMGeometrySegmentSink(double startMeasure, double endMeasure, SqlGeometryBuilder target, double tolerance, bool retainClipMeasure = false)
+ {
+ _target = target;
+ _clipStartMeasure = startMeasure;
+ _clipEndMeasure = endMeasure;
+ _tolerance = tolerance;
+ _retainClipMeasure = retainClipMeasure;
+ }
+
+ // Save the SRID for later
+ public void SetSrid(int srid)
+ {
+ _srid = srid;
+ _target.SetSrid(_srid);
+ }
+
+ // Start the geometry. Throw if it isn't a LineString.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (type != OpenGisGeometryType.LineString)
+ throw new ArgumentException("This operation may only be executed on LineString instances.");
+
+ _target.BeginGeometry(OpenGisGeometryType.LineString);
+ }
+
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ // Add the first shape point if either the start or end measure matches the clip start and end measure
+ if (m.EqualsTo(_clipStartMeasure) || m.EqualsTo(_clipEndMeasure))
+ {
+ _target.BeginFigure(x, y, z, m);
+ _started = true;
+ }
+
+ UpdateLastPoint(x, y, m);
+ }
+
+ // This is where the real work is done.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ // To unify code for ascending and descending measures - clipPointMeasure
+ double? clipPointMeasure = GetClipPointMeasure(m);
+ double newX, newY;
+
+ // If current measure is between start measure and end measure,
+ // we should add segment to the result linestring
+ if (m.IsWithinRange(_clipStartMeasure, _clipEndMeasure))
+ {
+ // if the geometry is started, just add the point to line
+ if (_started)
+ {
+ _target.AddLine(x, y, z, m);
+ }
+ // Else we need to begin the geom figure first
+ else
+ {
+ var isShapePoint = false;
+ // if clip point is shape point measure then add the point without computation
+ if (m.EqualsTo(clipPointMeasure))
+ {
+ _target.BeginFigure(x, y, null, m);
+ isShapePoint = true;
+ }
+ else
+ {
+ ComputePointCoordinates(clipPointMeasure, m, x, y, out newX, out newY);
+
+ // if computed point is within tolerance of last point then begin figure with last point
+ if (Ext.IsWithinTolerance(_previousX, _previousY, newX, newY, _tolerance))
+ _target.BeginFigure(_previousX, _previousY, null, _retainClipMeasure ? _clipStartMeasure : _previousM);
+
+ // check with current point against new computed point
+ else if (Ext.IsWithinTolerance(x, y, newX, newY, _tolerance))
+ {
+ _target.BeginFigure(x, y, null, m);
+ isShapePoint = true;
+ }
+
+ // else begin figure with clipped point
+ else
+ _target.BeginFigure(newX, newY, null, clipPointMeasure);
+ }
+
+ _started = true;
+ if (_clipStartMeasure.EqualsTo(_clipEndMeasure) || isShapePoint)
+ {
+ UpdateLastPoint(x, y, m);
+ return;
+ }
+
+ _target.AddLine(x, y, z, m);
+ }
+ }
+ // We may still need to add last segment,
+ // if current point is the first one after we passed range of interest
+ else
+ {
+ if (!_started)
+ {
+ var isShapePoint = false;
+ if (clipPointMeasure.IsWithinRange(m, _previousM))
+ {
+ ComputePointCoordinates(clipPointMeasure, m, x, y, out newX, out newY);
+
+ // if computed point is within tolerance of last point then begin figure with last point
+ if (Ext.IsWithinTolerance(_previousX, _previousY, newX, newY, _tolerance))
+ _target.BeginFigure(_previousX, _previousY, null, _retainClipMeasure ? clipPointMeasure : _previousM);
+ // check with current point against new computed point
+ else if (Ext.IsWithinTolerance(x, y, newX, newY, _tolerance))
+ {
+ _target.BeginFigure(x, y, null, _retainClipMeasure ? clipPointMeasure : m);
+ isShapePoint = true;
+ }
+ // else begin figure with clipped point
+ else
+ _target.BeginFigure(newX, newY, null, clipPointMeasure);
+
+ _started = true;
+ if (_clipStartMeasure.EqualsTo(_clipEndMeasure) || isShapePoint)
+ {
+ UpdateLastPoint(x, y, m);
+ return;
+ }
+ }
+ }
+ if (_started && !_finished)
+ {
+ // re calculate clip point measure as it can be changed from above condition.
+ clipPointMeasure = GetClipPointMeasure(m);
+
+ if (clipPointMeasure.IsWithinRange(m, _previousM))
+ {
+ ComputePointCoordinates(clipPointMeasure, m, x, y, out newX, out newY);
+
+ var isWithinLastPoint = Ext.IsWithinTolerance(_previousX, _previousY, newX, newY, _tolerance);
+ var isWithinCurrentPoint = Ext.IsWithinTolerance(x, y, newX, newY, _tolerance);
+
+ // if computed point is within tolerance of last point then skip
+ if (!isWithinLastPoint)
+ {
+ // if within current point then add current point
+ if (isWithinCurrentPoint)
+ _target.AddLine(x, y, null, _retainClipMeasure ? clipPointMeasure : m);
+ // else add computed point
+ else
+ _target.AddLine(newX, newY, null, clipPointMeasure);
+ }
+
+ _finished = true;
+ }
+ }
+ }
+
+ // re-assign the current co-ordinates to match for next iteration.
+ UpdateLastPoint(x, y, m);
+ }
+
+ private void UpdateLastPoint(double x, double y, double? m)
+ {
+ _previousX = x;
+ _previousY = y;
+ _previousM = m ?? 0;
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ // This is a NOP.
+ public void EndFigure()
+ {
+ _target.EndFigure();
+ }
+
+ // When we end, we'll make all of our output calls to our target.
+ // Here's also where we catch whether we've run off the end of our LineString.
+ public void EndGeometry()
+ {
+ _target.EndGeometry();
+ _started = false;
+ _finished = false;
+ _previousX = default(double);
+ _previousY = default(double);
+ _previousM = default(double);
+ }
+
+ ///
+ /// Gets the clip start or end measure.
+ ///
+ /// The current point measure.
+ /// Start or End Clip Measure
+ private double GetClipPointMeasure(double? currentPointMeasure)
+ {
+ double clipPointMeasure;
+ // increasing measures
+ if(_previousM < currentPointMeasure)
+ clipPointMeasure = _started ? Math.Max(_clipStartMeasure, _clipEndMeasure) : Math.Min(_clipStartMeasure, _clipEndMeasure);
+ // decreasing measures
+ else
+ clipPointMeasure = _started ? Math.Min(_clipStartMeasure, _clipEndMeasure) : Math.Max(_clipStartMeasure, _clipEndMeasure);
+
+ return clipPointMeasure;
+ }
+
+ ///
+ /// Computes the point coordinates.
+ ///
+ /// The compute point measure.
+ /// The current point measure.
+ /// The current x coordinate.
+ /// The current y coordinate.
+ /// The new x.
+ /// The new y.
+ private void ComputePointCoordinates(double? computePointMeasure, double? currentPointMeasure,
+ double currentXCoordinate, double currentYCoordinate, out double newX, out double newY)
+ {
+ newX = 0.0;
+ newY = 0.0;
+ if (!currentPointMeasure.HasValue || !computePointMeasure.HasValue)
+ return;
+
+ var currentM = (double) currentPointMeasure;
+ // The fraction of the way from start to end.
+ var fraction = ((double) computePointMeasure - _previousM) / (currentM - _previousM);
+ newX = (_previousX * (1 - fraction)) + (currentXCoordinate * fraction);
+ newY = (_previousY * (1 - fraction)) + (currentYCoordinate * fraction);
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/ConvertXYZ2XYMGeometrySink.cs b/src/Sinks/Geometry/ConvertXYZ2XYMGeometrySink.cs
new file mode 100644
index 0000000..eef80fc
--- /dev/null
+++ b/src/Sinks/Geometry/ConvertXYZ2XYMGeometrySink.cs
@@ -0,0 +1,40 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// Converts Z co-ordinate as measure.
+ ///
+ internal class ConvertXYZ2XYMGeometrySink : SqlGeometryBuilder
+ {
+ public override void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ if (m1.HasValue || m2.HasValue)
+ throw new ArgumentException(ErrorMessage.WKT3DOnly);
+
+ base.AddCircularArc(x1, y1, null, z1, x2, y2, null, z2);
+ }
+
+ public override void AddLine(double x, double y, double? z, double? m)
+ {
+ if (m.HasValue)
+ throw new ArgumentException(ErrorMessage.WKT3DOnly);
+
+ base.AddLine(x, y, null, z);
+ }
+
+ public override void BeginFigure(double x, double y, double? z, double? m)
+ {
+ if (m.HasValue)
+ throw new ArgumentException(ErrorMessage.WKT3DOnly);
+
+ base.BeginFigure(x, y, null, z);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Sinks/Geometry/ExtractPolygonFromLineGeometrySink.cs b/src/Sinks/Geometry/ExtractPolygonFromLineGeometrySink.cs
new file mode 100644
index 0000000..49e0331
--- /dev/null
+++ b/src/Sinks/Geometry/ExtractPolygonFromLineGeometrySink.cs
@@ -0,0 +1,118 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that extracts the polygon based upon the linestring.
+ /// Second segment measure is updated with offset difference.
+ ///
+ internal class ExtractPolygonFromLineGeometrySink : IGeometrySink110
+ {
+ private readonly SqlGeometryBuilder _target;
+ private List _points;
+ private int _srid;
+ private bool _doReverse;
+ private bool _isCircularString;
+
+ public ExtractPolygonFromLineGeometrySink(SqlGeometryBuilder geomBuilder, bool doReverse)
+ {
+ _target = geomBuilder;
+ _points = new List();
+ _doReverse = doReverse;
+ }
+
+ // Initialize MultiLine and sets srid.
+ public void SetSrid(int srid)
+ {
+ _srid = srid;
+ _target.SetSrid(_srid);
+ }
+
+ // Start the geometry.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ _isCircularString = type == OpenGisGeometryType.CircularString;
+ }
+
+ // Just add the points to the current line.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _points.Add(new LRSPoint(x, y, z, m, _srid));
+ }
+
+ // Just add the points to the current line.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _points.Add(new LRSPoint(x, y, z, m, _srid));
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ _points.Add(new LRSPoint(x1, y1, z1, m1, _srid));
+ _points.Add(new LRSPoint(x2, y2, z2, m2, _srid));
+ }
+
+ // Add the current line to the MULTILINESTRING collection
+ public void EndFigure()
+ {
+ // no-op
+ }
+
+ // This is a NO-OP
+ public void EndGeometry()
+ {
+
+ var first = true;
+ if (_doReverse)
+ _points.Reverse();
+
+ if (_isCircularString)
+ {
+ _target.BeginGeometry(OpenGisGeometryType.CurvePolygon);
+
+ for (var iterator = 0; iterator < _points.Count; iterator++)
+ {
+ var point = _points[iterator];
+ if (first)
+ {
+ _target.BeginFigure(point.X, point.Y, point.Z, point.M);
+ first = false;
+ }
+ else
+ {
+ var nextPoint = _points[iterator + 1];
+ _target.AddCircularArc(point.X, point.Y, point.Z, point.M, nextPoint.X, nextPoint.Y, nextPoint.Z, nextPoint.M);
+ iterator++;
+ }
+ }
+ }
+ else
+ {
+ _target.BeginGeometry(OpenGisGeometryType.Polygon);
+
+ foreach (var point in _points)
+ {
+ if (first)
+ {
+ _target.BeginFigure(point.X, point.Y, point.Z, point.M);
+ first = false;
+ }
+ else
+ {
+ _target.AddLine(point.X, point.Y, point.Z, point.M);
+ }
+ }
+ }
+
+ _target.EndFigure();
+ _target.EndGeometry();
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/GISTypeCheckGeometrySink.cs b/src/Sinks/Geometry/GISTypeCheckGeometrySink.cs
new file mode 100644
index 0000000..d5f9287
--- /dev/null
+++ b/src/Sinks/Geometry/GISTypeCheckGeometrySink.cs
@@ -0,0 +1,82 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that checks whether the geometry collection is of supported types.
+ ///
+ internal class GISTypeCheckGeometrySink : IGeometrySink110
+ {
+ private bool _isSupportedType;
+ private readonly OpenGisGeometryType[] _supportedTypes;
+
+ public GISTypeCheckGeometrySink(OpenGisGeometryType[] supportedTypes)
+ {
+ _isSupportedType = true;
+ _supportedTypes = supportedTypes;
+ }
+
+ ///
+ /// Returns true if input type is of specified supported types list.
+ ///
+ ///
+ public bool IsCompatible()
+ {
+ return _isSupportedType;
+ }
+
+ // This is a NOP.
+ public void SetSrid(int srid)
+ {
+ }
+
+ ///
+ /// Loop through each geometry types in geom collection and validate if the type is either POINT, LINESTRING, MULTILINESTRING
+ ///
+ /// Geometry Type
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (type == OpenGisGeometryType.GeometryCollection)
+ return;
+
+ // check if the type is of the supported types
+ if (_isSupportedType && !(type.Contains(_supportedTypes)))
+ {
+ _isSupportedType = false;
+ }
+ }
+
+ // This is a NOP.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+
+ }
+
+ // This is a NOP.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+
+ }
+
+ // This is a NOP.
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ }
+
+ // This is a NOP.
+ public void EndFigure()
+ {
+ }
+
+ // This is a NOP.
+ public void EndGeometry()
+ {
+
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/GeometryFilters.cs b/src/Sinks/Geometry/GeometryFilters.cs
new file mode 100644
index 0000000..bbbeea7
--- /dev/null
+++ b/src/Sinks/Geometry/GeometryFilters.cs
@@ -0,0 +1,340 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+
+ public class GeometryEmptyShapeFilter : IGeometrySink110
+ {
+ private readonly IGeometrySink110 _sink;
+ private readonly Queue _types = new Queue();
+ private bool _root = true;
+
+ public GeometryEmptyShapeFilter(IGeometrySink110 sink)
+ {
+ _sink = sink;
+ }
+
+ public void SetSrid(int srid)
+ {
+ _sink.SetSrid(srid);
+ }
+
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (_root)
+ {
+ _root = false;
+ _sink.BeginGeometry(type);
+ }
+ else
+ {
+ _types.Enqueue(type);
+ }
+ }
+
+ public void EndGeometry()
+ {
+ if (_types.Count > 0)
+ _types.Dequeue();
+ else
+ _sink.EndGeometry();
+ }
+
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ while (_types.Count > 0)
+ _sink.BeginGeometry(_types.Dequeue());
+ _sink.BeginFigure(x, y, z, m);
+ }
+
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _sink.AddLine(x, y, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ _sink.EndFigure();
+ }
+ }
+
+ public class GeometryPointFilter : IGeometrySink110
+ {
+ private readonly IGeometrySink110 _sink;
+ private int _depth;
+ private bool _root = true;
+
+ public GeometryPointFilter(IGeometrySink110 sink)
+ {
+ _sink = sink;
+ }
+
+ public void SetSrid(int srid)
+ {
+ _sink.SetSrid(srid);
+ }
+
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (type == OpenGisGeometryType.Point || type == OpenGisGeometryType.MultiPoint)
+ {
+ if (_root)
+ {
+ _root = false;
+ _sink.BeginGeometry(OpenGisGeometryType.GeometryCollection);
+ _sink.EndGeometry();
+ }
+ _depth++;
+ }
+ else
+ {
+ _sink.BeginGeometry(type);
+ }
+ }
+
+ public void EndGeometry()
+ {
+ if (_depth > 0)
+ _depth--;
+ else
+ _sink.EndGeometry();
+ }
+
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ if (_depth == 0)
+ _sink.BeginFigure(x, y, z, m);
+ }
+
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _sink.AddLine(x, y, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ if (_depth == 0)
+ _sink.EndFigure();
+ }
+ }
+
+ public class GeometryShortLineStringFilter : IGeometrySink110
+ {
+ private readonly IGeometrySink110 _sink;
+ private readonly double _tolerance;
+ private int _srid;
+ private bool _insideLineString;
+ private readonly List _figure = new List();
+
+ public GeometryShortLineStringFilter(IGeometrySink110 sink, double tolerance)
+ {
+ _sink = sink;
+ _tolerance = tolerance;
+ }
+
+ public void SetSrid(int srid)
+ {
+ _srid = srid;
+ _sink.SetSrid(srid);
+ }
+
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ _sink.BeginGeometry(type);
+ _insideLineString = type == OpenGisGeometryType.LineString;
+ }
+
+ public void EndGeometry()
+ {
+ _sink.EndGeometry();
+ }
+
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ if (_insideLineString)
+ {
+ _figure.Clear();
+ _figure.Add(new Vertex(x, y, z, m));
+ }
+ else
+ {
+ _sink.BeginFigure(x, y, z, m);
+ }
+ }
+
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ if (_insideLineString)
+ {
+ _figure.Add(new Vertex(x, y, z, m));
+ }
+ else
+ {
+ _sink.AddLine(x, y, z, m);
+ }
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ if (_insideLineString)
+ {
+ if (!IsShortLineString())
+ {
+ PopulateFigure(_sink);
+ }
+ }
+ else
+ {
+ _sink.EndFigure();
+ }
+ }
+
+ private bool IsShortLineString()
+ {
+ try
+ {
+ SqlGeometryBuilder b = new SqlGeometryBuilder();
+ b.SetSrid(_srid);
+ b.BeginGeometry(OpenGisGeometryType.LineString);
+ PopulateFigure(b);
+ b.EndGeometry();
+ return b.ConstructedGeometry.STLength().Value < _tolerance;
+ }
+ catch (ArgumentException) { }
+ catch (FormatException) { }
+ return true;
+ }
+
+ private void PopulateFigure(IGeometrySink110 sink)
+ {
+ _figure[0].BeginFigure(sink);
+ for (int i = 1; i < _figure.Count; i++)
+ _figure[i].AddLine(sink);
+ sink.EndFigure();
+ }
+ }
+
+ public class GeometryThinRingFilter : IGeometrySink110
+ {
+ private readonly IGeometrySink110 _sink;
+ private readonly double _tolerance;
+ private bool _insidePolygon;
+ private int _srid;
+ private readonly List _figure = new List();
+
+ public GeometryThinRingFilter(IGeometrySink110 sink, double tolerance)
+ {
+ _sink = sink;
+ _tolerance = tolerance;
+ }
+
+ public void SetSrid(int srid)
+ {
+ _srid = srid;
+ _sink.SetSrid(srid);
+ }
+
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ _sink.BeginGeometry(type);
+ _insidePolygon = type == OpenGisGeometryType.Polygon;
+ }
+
+ public void EndGeometry()
+ {
+ _sink.EndGeometry();
+ }
+
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ if (_insidePolygon)
+ {
+ _figure.Clear();
+ _figure.Add(new Vertex(x, y, z, m));
+ }
+ else
+ {
+ _sink.BeginFigure(x, y, z, m);
+ }
+ }
+
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ if (_insidePolygon)
+ {
+ _figure.Add(new Vertex(x, y, z, m));
+ }
+ else
+ {
+ _sink.AddLine(x, y, z, m);
+ }
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ if (_insidePolygon)
+ {
+ if (!IsThinRing())
+ {
+ PopulateFigure(_sink);
+ }
+ }
+ else
+ {
+ _sink.EndFigure();
+ }
+ }
+
+ private bool IsThinRing()
+ {
+ try
+ {
+ var builder = new SqlGeometryBuilder();
+ builder.SetSrid(_srid);
+ builder.BeginGeometry(OpenGisGeometryType.Polygon);
+ PopulateFigure(builder);
+ builder.EndGeometry();
+ var poly = builder.ConstructedGeometry.MakeValid();
+ return poly.STArea().Value < _tolerance * poly.STLength().Value;
+ }
+ catch (ArgumentException) { }
+ catch (FormatException) { }
+ return true;
+ }
+
+ private void PopulateFigure(IGeometrySink110 sink)
+ {
+ _figure[0].BeginFigure(sink);
+ for (int i = 1; i < _figure.Count; i++)
+ _figure[i].AddLine(sink);
+ sink.EndFigure();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Sinks/GeometryToPointGeographySink.cs b/src/Sinks/Geometry/GeometryToPointGeographySink.cs
similarity index 58%
rename from Sinks/GeometryToPointGeographySink.cs
rename to src/Sinks/Geometry/GeometryToPointGeographySink.cs
index 02f8cea..bd9aaa1 100644
--- a/Sinks/GeometryToPointGeographySink.cs
+++ b/src/Sinks/Geometry/GeometryToPointGeographySink.cs
@@ -1,64 +1,70 @@
-//------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation.
-//------------------------------------------------------------------------------
-using System;
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- //
- // Sink which extracts points from a geometry instance and forwards them to a geography sink
- //
- public sealed class GeometryToPointGeographySink : IGeometrySink
- {
- private readonly IGeographySink _sink;
- private int _count;
-
- public GeometryToPointGeographySink(IGeographySink sink)
- {
- _sink = sink;
- _count = 0;
- }
-
- public void BeginGeometry(OpenGisGeometryType type)
- {
- if (_count == 0)
- {
- _sink.BeginGeography(OpenGisGeographyType.MultiPoint);
- }
- _count++;
- }
-
- public void EndGeometry()
- {
- _count--;
- if (_count == 0)
- {
- _sink.EndGeography();
- }
- }
-
- public void BeginFigure(double x, double y, double? z, double? m)
- {
- _sink.BeginGeography(OpenGisGeographyType.Point);
- _sink.BeginFigure(y, x, z, m);
- _sink.EndFigure();
- _sink.EndGeography();
- }
-
- public void AddLine(double x, double y, double? z, double? m)
- {
- BeginFigure(x, y, z, m);
- }
-
- public void EndFigure()
- {
- // we ignore these calls
- }
-
- public void SetSrid(int srid)
- {
- _sink.SetSrid(srid);
- }
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// Sink which extracts points from a geometry instance and forwards them to a geography sink.
+ ///
+ public sealed class GeometryToPointGeographySink : IGeometrySink110
+ {
+ private readonly IGeographySink110 _sink;
+ private int _count;
+
+ public GeometryToPointGeographySink(IGeographySink110 sink)
+ {
+ _sink = sink;
+ _count = 0;
+ }
+
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (_count == 0)
+ {
+ _sink.BeginGeography(OpenGisGeographyType.MultiPoint);
+ }
+ _count++;
+ }
+
+ public void EndGeometry()
+ {
+ _count--;
+ if (_count == 0)
+ {
+ _sink.EndGeography();
+ }
+ }
+
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _sink.BeginGeography(OpenGisGeographyType.Point);
+ _sink.BeginFigure(y, x, z, m);
+ _sink.EndFigure();
+ _sink.EndGeography();
+ }
+
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ BeginFigure(x, y, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ // we ignore these calls
+ }
+
+ public void SetSrid(int srid)
+ {
+ _sink.SetSrid(srid);
+ }
+ }
}
\ No newline at end of file
diff --git a/Sinks/GeometryTransformer.cs b/src/Sinks/Geometry/GeometryTransformer.cs
similarity index 55%
rename from Sinks/GeometryTransformer.cs
rename to src/Sinks/Geometry/GeometryTransformer.cs
index 5afa5e5..f247360 100644
--- a/Sinks/GeometryTransformer.cs
+++ b/src/Sinks/Geometry/GeometryTransformer.cs
@@ -1,50 +1,56 @@
-//------------------------------------------------------------------------------
-// Copyright (c) 2008 Microsoft Corporation.
-//------------------------------------------------------------------------------
-
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- public sealed class GeometryTransformer : IGeometrySink
- {
- readonly IGeometrySink _sink;
- readonly AffineTransform _transform;
-
- public GeometryTransformer(IGeometrySink sink, AffineTransform transform)
- {
- _sink = sink;
- _transform = transform;
- }
-
- public void SetSrid(int srid)
- {
- _sink.SetSrid(srid);
- }
-
- public void BeginGeometry(OpenGisGeometryType type)
- {
- _sink.BeginGeometry(type);
- }
-
- public void BeginFigure(double x, double y, double? z, double? m)
- {
- _sink.BeginFigure(_transform.GetX(x, y), _transform.GetY(x, y), z, m);
- }
-
- public void AddLine(double x, double y, double? z, double? m)
- {
- _sink.AddLine(_transform.GetX(x, y), _transform.GetY(x, y), z, m);
- }
-
- public void EndFigure()
- {
- _sink.EndFigure();
- }
-
- public void EndGeometry()
- {
- _sink.EndGeometry();
- }
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types.SQL;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ public sealed class GeometryTransformer : IGeometrySink110
+ {
+ private readonly IGeometrySink110 _sink;
+ private readonly AffineTransform _transform;
+
+ public GeometryTransformer(IGeometrySink110 sink, AffineTransform transform)
+ {
+ _sink = sink;
+ _transform = transform;
+ }
+
+ public void SetSrid(int srid)
+ {
+ _sink.SetSrid(srid);
+ }
+
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ _sink.BeginGeometry(type);
+ }
+
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _sink.BeginFigure(_transform.GetX(x, y), _transform.GetY(x, y), z, m);
+ }
+
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _sink.AddLine(_transform.GetX(x, y), _transform.GetY(x, y), z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new System.Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ _sink.EndFigure();
+ }
+
+ public void EndGeometry()
+ {
+ _sink.EndGeometry();
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Sinks/Geometry/LineStringMergeGeometrySink.cs b/src/Sinks/Geometry/LineStringMergeGeometrySink.cs
new file mode 100644
index 0000000..0a59436
--- /dev/null
+++ b/src/Sinks/Geometry/LineStringMergeGeometrySink.cs
@@ -0,0 +1,95 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+using static SQLSpatialTools.Utility.SQLTypeConversions;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// Class implements a geometry sink that builds a line string by reducing from the _geometry in the range of points represented by indexes
+ ///
+ internal class LineStringMergeGeometrySink : IGeometrySink110
+ {
+ private readonly SqlGeometryBuilder _target; /* builder reference to store the reduced range of points */
+ private readonly bool _isFirstSegment; /* represent the _geometry would be the first part of the resultant geometry */
+ private readonly int _numPoints; /* counter indicates the index of the coordinates present in the _geometry */
+ private int _indexCounter; /* counter indicates the index of the coordinates present in the _geometry */
+
+ public LineStringMergeGeometrySink(SqlGeometryBuilder target, bool isFirstSegment, Numeric numPoints)
+ {
+ _target = target;
+ _isFirstSegment = isFirstSegment;
+ _numPoints = numPoints;
+ }
+
+ ///
+ /// Persisting the srid of the first geometry and using the
+ ///
+ ///
+ public void SetSrid(int srid)
+ {
+ if (_isFirstSegment)
+ _target.SetSrid(srid);
+ }
+
+ ///
+ /// Loop through each geometry types in geom collection and validate if the type is LINESTRING
+ ///
+ /// Geometry Type
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (type == OpenGisGeometryType.LineString)
+ {
+ if (_isFirstSegment)
+ _target.BeginGeometry(type);
+ }
+ else
+ throw new System.Exception("Line string is the only supported type");
+ }
+
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ ++_indexCounter;
+ if (_isFirstSegment)
+ _target.BeginFigure(x, y, z, m);
+ else
+ _target.AddLine(x, y, z, m);
+
+ }
+
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ ++_indexCounter;
+ // skip merging point for fist segment
+ if (!(_isFirstSegment && _numPoints == _indexCounter))
+ _target.AddLine(x, y, z, m);
+
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new System.Exception("Circular arc not yet implemented");
+
+ }
+
+ ///
+ /// End the figure, if its last point
+ ///
+ public void EndFigure()
+ {
+ if (!_isFirstSegment)
+ _target.EndFigure();
+ }
+
+ ///
+ /// End geometry construction
+ ///
+ public void EndGeometry()
+ {
+ if (!_isFirstSegment)
+ _target.EndGeometry();
+ }
+ }
+}
diff --git a/Sinks/LocateAlongGeometrySink.cs b/src/Sinks/Geometry/LocateAlongGeometrySink.cs
similarity index 59%
rename from Sinks/LocateAlongGeometrySink.cs
rename to src/Sinks/Geometry/LocateAlongGeometrySink.cs
index aca262f..af8d008 100644
--- a/Sinks/LocateAlongGeometrySink.cs
+++ b/src/Sinks/Geometry/LocateAlongGeometrySink.cs
@@ -1,102 +1,112 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-
-using Microsoft.SqlServer.Types;
-using System;
-
-namespace SQLSpatialTools
-{
- /**
- * This class implements a geometry sink that finds a point along a geography linestring instance and pipes
- * it to another sink.
- */
- class LocateAlongGeometrySink : IGeometrySink
- {
-
- double _distance; // The running count of how much further we have to go.
- SqlGeometry _lastPoint; // The last point in the LineString we have passed.
- SqlGeometry _foundPoint; // This is the point we're looking for, assuming it isn't null, we're done.
- int _srid; // The _srid we are working in.
- SqlGeometryBuilder _target; // Where we place our result.
-
- // We target another builder, to which we will send a point representing the point we find.
- // We also take a distance, which is the point along the input linestring we will travel.
- // Note that we only operate on LineString instances: anything else will throw an exception.
- public LocateAlongGeometrySink(double distance, SqlGeometryBuilder target)
- {
- _target = target;
- _distance = distance;
- }
-
- // Save the SRID for later
- public void SetSrid(int srid)
- {
- _srid = srid;
- }
-
- // Start the geometry. Throw if it isn't a LineString.
- public void BeginGeometry(OpenGisGeometryType type)
- {
- if (type != OpenGisGeometryType.LineString)
- throw new ArgumentException("This operation may only be executed on LineString instances.");
- }
-
- // Start the figure. Note that since we only operate on LineStrings, this should only be executed
- // once.
- public void BeginFigure(double latitude, double longitude, double? z, double? m)
- {
- // Memorize the point.
- _lastPoint = SqlGeometry.Point(latitude, longitude, _srid);
- }
-
- // This is where the real work is done.
- public void AddLine(double latitude, double longitude, double? z, double? m)
- {
- // If we've already found a point, then we're done. We just need to keep ignoring these
- // pesky calls.
- if (_foundPoint != null) return;
-
- // Make a point for our current position.
- SqlGeometry thisPoint = SqlGeometry.Point(latitude, longitude, _srid);
-
- // is the found point between this point and the last, or past this point?
- double length = thisPoint.STDistance(_lastPoint).Value;
- if (length < _distance)
- {
- // it's past this point---just step along the line
- _distance -= length;
- _lastPoint = thisPoint;
- }
- else
- {
- // now we need to do the hard work and find the point in between these two
- _foundPoint = Functions.InterpolateBetweenGeom(_lastPoint, thisPoint, _distance);
- }
- }
-
- // This is a NOP.
- public void EndFigure()
- {
- }
-
- // When we end, we'll make all of our output calls to our target.
- // Here's also where we catch whether we've run off the end of our LineString.
- public void EndGeometry()
- {
- if (_foundPoint != null)
- {
- // We could use a simple point constructor, but by targetting another sink we can use this
- // class in a pipeline.
- _target.SetSrid(_srid);
- _target.BeginGeometry(OpenGisGeometryType.Point);
- _target.BeginFigure(_foundPoint.STX.Value, _foundPoint.STY.Value);
- _target.EndFigure();
- _target.EndGeometry();
- }
- else
- {
- throw new ArgumentException("Distance provided is greated then the length of the LineString.");
- }
- }
-
- }
-}
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that finds a point along a geometry linestring instance and pipes
+ /// it to another sink.
+ ///
+ internal class LocateAlongGeometrySink : IGeometrySink110
+ {
+ private double _distance; // The running count of how much further we have to go.
+ private SqlGeometry _lastPoint; // The last point in the LineString we have passed.
+ private SqlGeometry _foundPoint; // This is the point we're looking for, assuming it isn't null, we're done.
+ private int _srid; // The _srid we are working in.
+ private readonly SqlGeometryBuilder _target; // Where we place our result.
+
+ // We target another builder, to which we will send a point representing the point we find.
+ // We also take a distance, which is the point along the input linestring we will travel.
+ // Note that we only operate on LineString instances: anything else will throw an exception.
+ public LocateAlongGeometrySink(double distance, SqlGeometryBuilder target)
+ {
+ _target = target;
+ _distance = distance;
+ }
+
+ // Save the SRID for later
+ public void SetSrid(int srid)
+ {
+ _srid = srid;
+ }
+
+ // Start the geometry. Throw if it isn't a LineString.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (type != OpenGisGeometryType.LineString)
+ throw new ArgumentException("This operation may only be executed on LineString instances.");
+ }
+
+ // Start the figure. Note that since we only operate on LineStrings, this should only be executed
+ // once.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ // Memorize the point.
+ _lastPoint = SqlGeometry.Point(x, y, _srid);
+ if (_distance.EqualsTo(0))
+ _foundPoint = _lastPoint;
+ }
+
+ // This is where the real work is done.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ // If we've already found a point, then we're done. We just need to keep ignoring these
+ // pesky calls.
+ if (_foundPoint != null)
+ return;
+
+ // Make a point for our current position.
+ SqlGeometry thisPoint = SqlGeometry.Point(x, y, _srid);
+
+ // is the found point between this point and the last, or past this point?
+ double length = thisPoint.STDistance(_lastPoint).Value;
+ if (length < _distance)
+ {
+ // it's past this point---just step along the line
+ _distance -= length;
+ _lastPoint = thisPoint;
+ }
+ else
+ {
+ // now we need to do the hard work and find the point in between these two
+ _foundPoint = Functions.General.Geometry.InterpolateBetweenGeom(_lastPoint, thisPoint, _distance);
+ }
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ // This is a NOP.
+ public void EndFigure()
+ {
+ }
+
+ // When we end, we'll make all of our output calls to our target.
+ // Here's also where we catch whether we've run off the end of our LineString.
+ public void EndGeometry()
+ {
+ if (_foundPoint != null)
+ {
+ // We could use a simple point constructor, but by targeting another sink we can use this
+ // class in a pipeline.
+ _target.SetSrid(_srid);
+ _target.BeginGeometry(OpenGisGeometryType.Point);
+ _target.BeginFigure(_foundPoint.STX.Value, _foundPoint.STY.Value);
+ _target.EndFigure();
+ _target.EndGeometry();
+ }
+ else
+ {
+ throw new ArgumentException("Distance provided is greater than the length of the LineString.");
+ }
+ }
+
+ }
+}
diff --git a/src/Sinks/Geometry/LocateMAlongGeometrySink.cs b/src/Sinks/Geometry/LocateMAlongGeometrySink.cs
new file mode 100644
index 0000000..a3356cd
--- /dev/null
+++ b/src/Sinks/Geometry/LocateMAlongGeometrySink.cs
@@ -0,0 +1,133 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+using System;
+using SQLSpatialTools.Utility;
+using Ext = SQLSpatialTools.Utility.SpatialExtensions;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that finds a point along a geometry linestring instance and pipes
+ /// it to another sink.
+ ///
+ internal class LocateMAlongGeometrySink : IGeometrySink110
+ {
+ private readonly double _measure; // The running count of how much further we have to go.
+ private readonly double _tolerance; // The tolerance.
+ private readonly SqlGeometryBuilder _target; // Where we place our result.
+ private int _srid; // The srid we are working in.
+ private SqlGeometry _lastPoint; // The last point in the LineString we have passed.
+ private SqlGeometry _foundPoint; // This is the point we're looking for, assuming it isn't null, we're done.
+ public bool IsPointDerived;
+ public bool IsShapePoint;
+
+ // We target another builder, to which we will send a point representing the point we find.
+ // We also take a measure, which is the point along the input linestring we will travel to.
+ // Note that we only operate on LineString instances: anything else will throw an exception.
+ public LocateMAlongGeometrySink(double measure, SqlGeometryBuilder target, double tolerance = Constants.Tolerance)
+ {
+ _target = target;
+ _measure = measure;
+ _tolerance = tolerance;
+ IsPointDerived = false;
+ }
+
+ // Save the SRID for later
+ public void SetSrid(int srid)
+ {
+ _srid = srid;
+ }
+
+ // Start the geometry. Throw if it isn't a LineString.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ }
+
+ // Start the figure. Note that since we only operate on LineStrings, this should only be executed
+ // once.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ if (_foundPoint != null)
+ return;
+
+ // Memorize the point.
+ _lastPoint = CheckShapePointAndGet(m, Ext.GetPoint(x, y, z, m, _srid));
+ }
+
+ // This is where the real work is done.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ // If we've already found a point, then we're done. We just need to keep ignoring these
+ // pesky calls.
+ if (_foundPoint != null)
+ return;
+
+ // Make a point for our current position.
+ var thisPoint = CheckShapePointAndGet(m, Ext.GetPoint(x, y, z, m, _srid));
+
+ // is the found point between this point and the last, or past this point?
+ if (m != null && _measure.IsWithinRange(_lastPoint.M.Value, m.Value))
+ {
+ // now we need to do the hard work and find the point in between these two
+ _foundPoint = Functions.LRS.Geometry.InterpolateBetweenGeom(_lastPoint, thisPoint, _measure);
+ if (_lastPoint.IsWithinTolerance(_foundPoint, _tolerance))
+ {
+ _foundPoint = _lastPoint;
+ IsShapePoint = true;
+ }
+ else if (thisPoint.IsWithinTolerance(_foundPoint, _tolerance))
+ {
+ _foundPoint = thisPoint;
+ IsShapePoint = true;
+ }
+ }
+ else
+ {
+ // it's past this point---just step along the line
+ _lastPoint = thisPoint;
+ }
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ ///
+ /// Checks if the point is a shape point.
+ ///
+ /// The m.
+ /// The geometry.
+ ///
+ private SqlGeometry CheckShapePointAndGet(double? m, SqlGeometry geometry)
+ {
+ IsShapePoint = m.EqualsTo(_measure);
+ if (IsShapePoint)
+ _foundPoint = geometry;
+ return geometry;
+ }
+
+ // This is a NOP.
+ public void EndFigure()
+ {
+ }
+
+ // When we end, we'll make all of our output calls to our target.
+ // Here's also where we catch whether we've run off the end of our LineString.
+ public void EndGeometry()
+ {
+ if (_foundPoint == null || IsPointDerived) return;
+ // We could use a simple point constructor, but by targeting another sink we can use this
+ // class in a pipeline.
+ _target.SetSrid(_srid);
+ _target.BeginGeometry(OpenGisGeometryType.Point);
+ _target.BeginFigure(_foundPoint.STX.Value, _foundPoint.STY.Value, _foundPoint.Z.IsNull ? (double?)null : _foundPoint.Z.Value, _foundPoint.M.Value);
+ _target.EndFigure();
+ _target.EndGeometry();
+ IsPointDerived = true;
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/MultiplyMeasureGeometrySink.cs b/src/Sinks/Geometry/MultiplyMeasureGeometrySink.cs
new file mode 100644
index 0000000..2568b79
--- /dev/null
+++ b/src/Sinks/Geometry/MultiplyMeasureGeometrySink.cs
@@ -0,0 +1,78 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+using System;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that multiplies the measure values of input geometry.
+ ///
+ internal class MultiplyMeasureGeometrySink : IGeometrySink110
+ {
+ private readonly SqlGeometryBuilder _target;
+ private readonly double _scaleMeasure;
+
+ ///
+ /// Loop through each geometry types LINESTRING and MULTILINESTRING and scales the measure by given magnitude.
+ ///
+ ///
+ ///
+ public MultiplyMeasureGeometrySink(SqlGeometryBuilder target, double scaleMeasure)
+ {
+ _target = target;
+ _scaleMeasure = scaleMeasure;
+ }
+
+ // Just pass through target.
+ public void SetSrid(int srid)
+ {
+ _target.SetSrid(srid);
+ }
+
+ // Just pass through target.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ _target.BeginGeometry(type);
+ }
+
+ // Just add the points with updated measure.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _target.BeginFigure(x, y, z, GetUpdatedMeasure(m));
+ }
+
+ // Just add the points with updated measure.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _target.AddLine(x, y, z, GetUpdatedMeasure(m));
+ }
+
+ // Just pass through target.
+ public void EndFigure()
+ {
+ _target.EndFigure();
+ }
+
+ // Just pass through target.
+ public void EndGeometry()
+ {
+ _target.EndGeometry();
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ private double? GetUpdatedMeasure(double? m)
+ {
+ double? measure = null;
+ if (m.HasValue)
+ measure = (double)m * _scaleMeasure;
+ return measure;
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/PopulateGeometryMeasuresSink.cs b/src/Sinks/Geometry/PopulateGeometryMeasuresSink.cs
new file mode 100644
index 0000000..382a2d9
--- /dev/null
+++ b/src/Sinks/Geometry/PopulateGeometryMeasuresSink.cs
@@ -0,0 +1,131 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that populate measures for each point in a geometry .
+ ///
+ internal class PopulateGeometryMeasuresSink : IGeometrySink110
+ {
+ private SqlGeometry _lastPoint;
+ private SqlGeometry _thisPoint;
+
+ private LRSMultiLine _lines;
+ private LRSLine _currentLine;
+
+ private readonly double _startMeasure;
+ private readonly double _endMeasure;
+ private readonly double _totalLength;
+
+ private bool _isMultiLine;
+ private int _lineCounter;
+ private int _srid; // The _srid we are working in.
+ private double _currentLength;
+ private double _currentPointM;
+ private SqlGeometry _target; // Where we place our result.
+
+ ///
+ /// Gets the constructed geometry.
+ ///
+ ///
+ public SqlGeometry GetConstructedGeom()
+ {
+ return _target;
+ }
+
+ // We target another builder, to which we will send a point representing the point we find.
+ // We also take a distance, which is the point along the input linestring we will travel.
+ // Note that we only operate on LineString instances: anything else will throw an exception.
+ public PopulateGeometryMeasuresSink(double startMeasure, double endMeasure, double length)
+ {
+ _startMeasure = startMeasure;
+ _endMeasure = endMeasure;
+ _totalLength = length;
+ _isMultiLine = false;
+ _lineCounter = 0;
+ _currentPointM = startMeasure;
+ }
+
+ // Initialize MultiLine and sets srid.
+ public void SetSrid(int srid)
+ {
+ _lines = new LRSMultiLine(srid);
+ _srid = srid;
+ }
+
+ // Start geometry and check if the type is of the supported types
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (type == OpenGisGeometryType.MultiLineString)
+ _isMultiLine = true;
+ else if (type == OpenGisGeometryType.LineString)
+ _lineCounter++;
+ }
+
+
+ // This operates on LineStrings, multi linestring
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _currentLine = new LRSLine(_srid);
+ _currentLine.AddPoint(x, y, null, _currentPointM);
+
+ // Memorize the starting point.
+ _lastPoint = SqlGeometry.Point(x, y, _srid);
+ }
+
+ // This is where the real work is done.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _thisPoint = SqlGeometry.Point(x, y, _srid);
+ _currentLength += _lastPoint.STDistance(_thisPoint).Value;
+ _currentLine.AddPoint(x, y, null, GetCurrentMeasure());
+
+ // reset the last point with the current point.
+ _lastPoint = _thisPoint;
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ // This is a NOP.
+ public void EndFigure()
+ {
+
+ }
+
+ // When we end, we'll make all of our output calls to our target.
+ public void EndGeometry()
+ {
+ // if not multi line then add the current line to the collection.
+ if (!_isMultiLine)
+ _lines.AddLine(_currentLine);
+
+ // if line counter is 0 then it is multiline
+ // if 1 then it is linestring
+ if (_lineCounter == 0 || !_isMultiLine)
+ {
+ _target = _lines.ToSqlGeometry();
+ }
+ else
+ {
+ _lines.AddLine(_currentLine);
+ // reset the line counter so that the child line strings chaining is done and return to base multiline type
+ _lineCounter--;
+ }
+ }
+
+ private double GetCurrentMeasure()
+ {
+ _currentPointM = _startMeasure + (_currentLength / _totalLength) * (_endMeasure - _startMeasure);
+ return _currentPointM;
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/ResetMGeometrySink.cs b/src/Sinks/Geometry/ResetMGeometrySink.cs
new file mode 100644
index 0000000..be53140
--- /dev/null
+++ b/src/Sinks/Geometry/ResetMGeometrySink.cs
@@ -0,0 +1,73 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+using System;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that resets M value to null.
+ ///
+ internal class ResetMGeometrySink : IGeometrySink110
+ {
+ private readonly SqlGeometryBuilder _target; // Where we place our result.
+
+ public ResetMGeometrySink(SqlGeometryBuilder target)
+ {
+ _target = target;
+ }
+
+ ///
+ /// Save the SRID for later
+ ///
+ /// Spatial Reference Identifier
+ public void SetSrid(int srid)
+ {
+ _target.SetSrid(srid);
+ }
+
+ ///
+ /// Start the geometry
+ ///
+ /// Geometry Type
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ _target.BeginGeometry(type);
+ }
+
+ ///
+ /// Start the figure.
+ /// Note that since we only operate on LineStrings, Multilinestring and Point these should only be executed.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _target.BeginFigure(x, y, z, null);
+ }
+
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _target.AddLine(x, y, z, null);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ _target.EndFigure();
+ }
+
+ public void EndGeometry()
+ {
+ _target.EndGeometry();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Sinks/Geometry/ReverseAndTranslateGeometrySink.cs b/src/Sinks/Geometry/ReverseAndTranslateGeometrySink.cs
new file mode 100644
index 0000000..56b35dd
--- /dev/null
+++ b/src/Sinks/Geometry/ReverseAndTranslateGeometrySink.cs
@@ -0,0 +1,101 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that reverses the input geometry and translate the measure
+ ///
+ internal class ReverseAndTranslateGeometrySink : IGeometrySink110
+ {
+ private SqlGeometryBuilder _target;
+ private readonly double _translateMeasure;
+ private LRSMultiLine _lines;
+ private LRSLine _currentLine;
+ private bool _isMultiLine;
+ private int _lineCounter;
+ private int _srid;
+
+ ///
+ /// Loop through each geometry types LINESTRING and MULTILINESTRING and reverse and translate measure it accordingly.
+ ///
+ ///
+ ///
+ public ReverseAndTranslateGeometrySink(SqlGeometryBuilder target, double translateMeasure)
+ {
+ _target = target;
+ _translateMeasure = translateMeasure;
+ _isMultiLine = false;
+ _lineCounter = 0;
+ }
+
+ // Initialize MultiLine and sets srid.
+ public void SetSrid(int srid)
+ {
+ _lines = new LRSMultiLine(srid);
+ _srid = srid;
+ }
+
+ // Check for types and begin geometry accordingly.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (type == OpenGisGeometryType.MultiLineString)
+ _isMultiLine = true;
+ if (type == OpenGisGeometryType.LineString)
+ _lineCounter++;
+ }
+
+ // Just add the points to the current line segment.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _currentLine = new LRSLine(_srid);
+ _currentLine.AddPoint(x, y, z, m);
+ }
+
+ // Just add the points to the current line segment.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _currentLine.AddPoint(x, y, z, m);
+ }
+
+ // Reverse the points at the end of figure.
+ public void EndFigure()
+ {
+ _currentLine.ReversePoints();
+ }
+
+ // This is where real work is done.
+ public void EndGeometry()
+ {
+ // if not multi line then add the current line to the collection.
+ if (!_isMultiLine)
+ _lines.AddLine(_currentLine);
+
+ // if line counter is 0 then it is multiline
+ // if 1 then it is linestring
+ if (_lineCounter == 0 || !_isMultiLine)
+ {
+ // reverse the line before constructing the geometry
+ _lines.ReversLines();
+ _lines.TranslateMeasure(_translateMeasure);
+ _lines.ToSqlGeometry(ref _target);
+ }
+ else
+ {
+ _lines.AddLine(_currentLine);
+ // reset the line counter so that the child line strings chaining is done and return to base multiline type
+ _lineCounter--;
+ }
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/ReverseLinearGeometrySink.cs b/src/Sinks/Geometry/ReverseLinearGeometrySink.cs
new file mode 100644
index 0000000..f390d63
--- /dev/null
+++ b/src/Sinks/Geometry/ReverseLinearGeometrySink.cs
@@ -0,0 +1,99 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that reverses the input geometry.
+ ///
+ internal class ReverseLinearGeometrySink : IGeometrySink110
+ {
+ private SqlGeometryBuilder _target;
+ private LRSMultiLine _lines;
+ private LRSLine _currentLine;
+ private bool _isMultiLine;
+ private int _lineCounter;
+ private int _srid;
+
+ ///
+ /// Loop through each geometry types LINESTRING and MULTILINESTRING and reverse it accordingly.
+ ///
+ ///
+ public ReverseLinearGeometrySink(SqlGeometryBuilder target)
+ {
+ _target = target;
+ _isMultiLine = false;
+ _lineCounter = 0;
+ }
+
+ // Initialize MultiLine and sets srid.
+ public void SetSrid(int srid)
+ {
+ _lines = new LRSMultiLine(srid);
+ _srid = srid;
+ }
+
+ // Check for types and begin geometry accordingly.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ // check if the type is of the supported types
+ if (type == OpenGisGeometryType.MultiLineString)
+ _isMultiLine = true;
+
+ if (type == OpenGisGeometryType.LineString)
+ _lineCounter++;
+ }
+
+ // Just add the points to the current line segment.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _currentLine = new LRSLine(_srid);
+ _currentLine.AddPoint(x, y, z, m);
+ }
+
+ // Just add the points to the current line segment.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _currentLine.AddPoint(x, y, z, m);
+ }
+
+ // Reverse the points at the end of figure.
+ public void EndFigure()
+ {
+ _currentLine.ReversePoints();
+ }
+
+ // This is where real work is done.
+ public void EndGeometry()
+ {
+ // if not multi line then add the current line to the collection.
+ if (!_isMultiLine)
+ _lines.AddLine(_currentLine);
+
+ // if line counter is 0 then it is multiline
+ // if 1 then it is linestring
+ if (_lineCounter == 0 || !_isMultiLine)
+ {
+ // reverse the line before constructing the geometry
+ _lines.ReversLines();
+ _lines.ToSqlGeometry(ref _target);
+ }
+ else
+ {
+ _lines.AddLine(_currentLine);
+ // reset the line counter so that the child line strings chaining is done and return to base multiline type
+ _lineCounter--;
+ }
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/ScaleGeometrySink.cs b/src/Sinks/Geometry/ScaleGeometrySink.cs
new file mode 100644
index 0000000..2692d69
--- /dev/null
+++ b/src/Sinks/Geometry/ScaleGeometrySink.cs
@@ -0,0 +1,92 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that scales the measure values of input geometry.
+ ///
+ internal class ScaleGeometrySink : IGeometrySink110
+ {
+ private readonly SqlGeometryBuilder _target;
+ private readonly double _startMeasure, _endMeasure, _newStartMeasure, _newEndMeasure, _shiftMeasure;
+ private bool _isPoint;
+
+ ///
+ /// Loop through each geometry types LINESTRING and MULTILINESTRING and translates its measure.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public ScaleGeometrySink(SqlGeometryBuilder target, double startMeasure, double endMeasure, double newStartMeasure, double newEndMeasure, double shiftMeasure)
+ {
+ _target = target;
+ _startMeasure = startMeasure;
+ _endMeasure = endMeasure;
+ _newStartMeasure = newStartMeasure;
+ _newEndMeasure = newEndMeasure;
+ _shiftMeasure = shiftMeasure;
+ }
+
+ // Just pass through target.
+ public void SetSrid(int srid)
+ {
+ _target.SetSrid(srid);
+ }
+
+ // Just pass through target.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (type == OpenGisGeometryType.Point)
+ _isPoint = true;
+
+ _target.BeginGeometry(type);
+ }
+
+ // Just add the points with updated measure.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ if (_isPoint)
+ _target.BeginFigure(x, y, z, _newEndMeasure + _shiftMeasure);
+ else
+ _target.BeginFigure(x, y, z, GetUpdatedMeasure(m));
+ }
+
+ // Just add the points with updated measure.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _target.AddLine(x, y, z, GetUpdatedMeasure(m));
+ }
+
+ // Just pass through target.
+ public void EndFigure()
+ {
+ _target.EndFigure();
+ }
+
+ // Just pass through target.
+ public void EndGeometry()
+ {
+ _target.EndGeometry();
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ private double GetUpdatedMeasure(double? m)
+ {
+ var mValue = m ?? 0.0;
+ return ((mValue - _startMeasure) * ((_newEndMeasure - _newStartMeasure) / (_endMeasure - _startMeasure))) +
+ _newStartMeasure + _shiftMeasure;
+ }
+ }
+}
diff --git a/Sinks/ShiftGeometrySink.cs b/src/Sinks/Geometry/ShiftGeometrySink.cs
similarity index 63%
rename from Sinks/ShiftGeometrySink.cs
rename to src/Sinks/Geometry/ShiftGeometrySink.cs
index 2ccc48b..c5f404f 100644
--- a/Sinks/ShiftGeometrySink.cs
+++ b/src/Sinks/Geometry/ShiftGeometrySink.cs
@@ -1,62 +1,69 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- /**
- * This class implements a geometry sink that will shift an input geometry by a given amount in the x and
- * y directions. It directs its output to another sink, and can therefore be used in a pipeline if desired.
- */
- public class ShiftGeometrySink : IGeometrySink
- {
- private readonly IGeometrySink _target; // the target sink
- private readonly double _xShift; // How much to shift in the x direction.
- private readonly double _yShift; // How much to shift in the y direction.
-
- // We take an amount to shift in the x and y directions, as well as a target sink, to which
- // we will pipe our result.
- public ShiftGeometrySink(double xShift, double yShift, IGeometrySink target)
- {
- _target = target;
- _xShift = xShift;
- _yShift = yShift;
- }
-
- // Just pass through without change.
- public void SetSrid(int srid)
- {
- _target.SetSrid(srid);
- }
-
- // Just pass through without change.
- public void BeginGeometry(OpenGisGeometryType type)
- {
- _target.BeginGeometry(type);
- }
-
- // Each BeginFigure call will just move the start point by the required amount.
- public void BeginFigure(double x, double y, double? z, double? m)
- {
- _target.BeginFigure(x + _xShift, y + _yShift, z, m);
- }
-
- // Each AddLine call will just move the endpoint by the required amount.
- public void AddLine(double x, double y, double? z, double? m)
- {
- _target.AddLine(x + _xShift, y + _yShift, z, m);
- }
-
- // Just pass through without change.
- public void EndFigure()
- {
- _target.EndFigure();
- }
-
- // Just pass through without change.
- public void EndGeometry()
- {
- _target.EndGeometry();
- }
- }
-}
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that will shift an input geometry by a given amount in the x and
+ /// y directions. It directs its output to another sink, and can therefore be used in a pipeline if desired.
+ ///
+ public class ShiftGeometrySink : IGeometrySink110
+ {
+ private readonly IGeometrySink110 _target; // the target sink
+ private readonly double _xShift; // How much to shift in the x direction.
+ private readonly double _yShift; // How much to shift in the y direction.
+
+ // We take an amount to shift in the x and y directions, as well as a target sink, to which
+ // we will pipe our result.
+ public ShiftGeometrySink(double xShift, double yShift, IGeometrySink110 target)
+ {
+ _target = target;
+ _xShift = xShift;
+ _yShift = yShift;
+ }
+
+ // Just pass through without change.
+ public void SetSrid(int srid)
+ {
+ _target.SetSrid(srid);
+ }
+
+ // Just pass through without change.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ _target.BeginGeometry(type);
+ }
+
+ // Each BeginFigure call will just move the start point by the required amount.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _target.BeginFigure(x + _xShift, y + _yShift, z, m);
+ }
+
+ // Each AddLine call will just move the endpoint by the required amount.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _target.AddLine(x + _xShift, y + _yShift, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new System.Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ // Just pass through without change.
+ public void EndFigure()
+ {
+ _target.EndFigure();
+ }
+
+ // Just pass through without change.
+ public void EndGeometry()
+ {
+ _target.EndGeometry();
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/SplitGeometrySegmentSink.cs b/src/Sinks/Geometry/SplitGeometrySegmentSink.cs
new file mode 100644
index 0000000..d70bbd1
--- /dev/null
+++ b/src/Sinks/Geometry/SplitGeometrySegmentSink.cs
@@ -0,0 +1,209 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that split the geometry LINESTRING and MULTILINESTRING into two segments based on split point.
+ ///
+ internal class SplitGeometrySegmentSink : IGeometrySink110
+ {
+ private readonly double _splitPointMeasure;
+ private readonly SqlGeometry _splitPoint;
+
+ public SqlGeometry Segment1; // Where we place our result.
+ public SqlGeometry Segment2; // Where we place our result.
+
+ private LRSMultiLine _segment1;
+ private LRSMultiLine _segment2;
+ private LRSLine _currentLineForSegment1;
+ private LRSLine _currentLineForSegment2;
+
+ private int _srid, _lineCounter;
+ private bool _isMultiLine;
+ private bool _splitPointReached;
+ private double _lastM;
+
+ // Initialize Split Geom Sink with split point
+ public SplitGeometrySegmentSink(SqlGeometry splitPoint)
+ {
+ _splitPoint = splitPoint;
+ _isMultiLine = false;
+ _lineCounter = 0;
+ _splitPointMeasure = splitPoint.HasM ? splitPoint.M.Value : 0;
+ }
+
+ // Save the SRID for later
+ public void SetSrid(int srid)
+ {
+ _segment1 = new LRSMultiLine(srid);
+ _segment2 = new LRSMultiLine(srid);
+ _srid = srid;
+ }
+
+ private bool IsEqualToSplitMeasure(double? currentMeasure)
+ {
+ return currentMeasure.EqualsTo(_splitPoint.M.Value);
+ }
+
+ // Start the geometry.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ if (type == OpenGisGeometryType.MultiLineString)
+ _isMultiLine = true;
+ if (type == OpenGisGeometryType.LineString)
+ _lineCounter++;
+ }
+
+ // Start the figure.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _currentLineForSegment1 = new LRSLine(_srid);
+ _currentLineForSegment2 = new LRSLine(_srid);
+
+ // just add it to the second segment once split point is reached
+ if (m != null && (_splitPointReached && m.Value.NotEqualsTo(_splitPointMeasure)))
+ {
+ _currentLineForSegment2.AddPoint(x, y, z, m);
+ return;
+ }
+
+ if (m < _splitPointMeasure)
+ _currentLineForSegment1.AddPoint(x, y, null, m);
+ else if (m > _splitPointMeasure || IsEqualToSplitMeasure(m))
+ {
+ _currentLineForSegment2.AddPoint(x, y, null, m);
+ _splitPointReached = IsEqualToSplitMeasure(m);
+ }
+
+ if (m != null) _lastM = (double) m;
+ }
+
+ // This is where the real work is done.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ // just add it to the second segment once split point is reached
+ if (m != null && (_splitPointReached && m.Value.NotEqualsTo(_splitPointMeasure)))
+ {
+ _currentLineForSegment2.AddPoint(x, y, z, m);
+ return;
+ }
+
+ // If current measure is less than split measure; then add it to the first segment.
+ if (m < _splitPointMeasure)
+ {
+ _currentLineForSegment1.AddPoint(x, y, z, m);
+ }
+
+ // split measure in between last point measure and current point measure.
+ else if (_splitPointMeasure > _lastM && _splitPointMeasure < m)
+ {
+ _currentLineForSegment1.AddPoint(_splitPoint);
+ _currentLineForSegment2.AddPoint(_splitPoint);
+ _currentLineForSegment2.AddPoint(x, y, z, m);
+ _splitPointReached = true;
+ }
+
+ // if current measure is equal to split measure; then it is a shape point
+ else if (m != null && (IsEqualToSplitMeasure(m) && _lastM.NotEqualsTo(m.Value)))
+ {
+ _currentLineForSegment1.AddPoint(x, y, z, m);
+ _currentLineForSegment2.AddPoint(x, y, z, m);
+ _splitPointReached = true;
+ }
+
+ // If current measure is greater than split measure; then add it to the second segment.
+ else if (m > _splitPointMeasure)
+ {
+ _currentLineForSegment2.AddPoint(x, y, z, m);
+ }
+
+ // reassign current measure to last measure
+ if (m != null) _lastM = (double) m;
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ // This is a NOP.
+ public void EndFigure()
+ {
+ }
+
+ // add segments to target
+ public void EndGeometry()
+ {
+ // if not multi line then add the current line to the collection.
+ if (!_isMultiLine)
+ {
+ _segment1.AddLine(_currentLineForSegment1);
+ _segment2.AddLine(_currentLineForSegment2);
+ }
+
+ // if line counter is 0 then it is multiline
+ // if 1 then it is linestring
+ if (_lineCounter == 0 || !_isMultiLine)
+ {
+ Segment1 = _segment1.ToSqlGeometry();
+
+ // TODO:: Messy logic to be meet as per Oracle; need to be re-factored
+ // if second is a multi line
+ // end measure of first line > end measure of last line
+ // then consider only first line
+ // by locating the point
+ if (!_segment1.IsEmpty && _segment2.IsMultiLine)
+ {
+ var endSegmentEndM = _segment2.GetLastLine().GetEndPointM();
+ var startSegmentStartM = _segment2.GetFirstLine().GetEndPointM();
+
+ if (startSegmentStartM > endSegmentEndM)
+ {
+ var trimmedLine = _segment2.GetFirstLine();
+ var newLRSLine = new LRSLine(_srid);
+
+ // add points up to end segment measure
+ foreach (var point in trimmedLine)
+ {
+ if (point.M < endSegmentEndM)
+ newLRSLine.AddPoint(point);
+ }
+
+ // add the end point
+ if (endSegmentEndM.EqualsTo(_splitPointMeasure))
+ newLRSLine.AddPoint(_splitPoint);
+ else
+ newLRSLine.AddPoint(trimmedLine.LocatePoint(endSegmentEndM, newLRSLine.GetEndPoint()));
+
+ Segment2 = newLRSLine.ToSqlGeometry();
+ }
+ // if end segment measure is equal to split measure; then return the split alone for second segment
+ else if (endSegmentEndM.EqualsTo(_splitPointMeasure))
+ Segment2 = _splitPoint;
+ else
+ Segment2 = _segment2.ToSqlGeometry();
+ }
+ else
+ Segment2 = _segment2.ToSqlGeometry();
+ }
+ else
+ {
+ if (_currentLineForSegment1.IsLine)
+ _segment1.AddLine(_currentLineForSegment1);
+
+ if (_currentLineForSegment2.IsLine)
+ _segment2.AddLine(_currentLineForSegment2);
+
+ // reset the line counter so that the child line strings chaining is done and return to base multiline type
+ _lineCounter--;
+ }
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/TranslateMeasureGeometrySink.cs b/src/Sinks/Geometry/TranslateMeasureGeometrySink.cs
new file mode 100644
index 0000000..50479cd
--- /dev/null
+++ b/src/Sinks/Geometry/TranslateMeasureGeometrySink.cs
@@ -0,0 +1,75 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a geometry sink that translates the measure values of input geometry.
+ ///
+ internal class TranslateMeasureGeometrySink : IGeometrySink110
+ {
+ private readonly SqlGeometryBuilder _target;
+ private readonly double _translateMeasure;
+
+ ///
+ /// Loop through each geometry types LINESTRING and MULTILINESTRING and translates its measure.
+ ///
+ ///
+ ///
+ public TranslateMeasureGeometrySink(SqlGeometryBuilder target, double translateMeasure)
+ {
+ _target = target;
+ _translateMeasure = translateMeasure;
+ }
+
+ // Just pass through target.
+ public void SetSrid(int srid)
+ {
+ _target.SetSrid(srid);
+ }
+
+ // Just pass through target.
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ _target.BeginGeometry(type);
+ }
+
+ // Just add the points with updated measure.
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _target.BeginFigure(x, y, z, GetUpdatedMeasure(m));
+ }
+
+ // Just add the points with updated measure.
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _target.AddLine(x, y, z, GetUpdatedMeasure(m));
+ }
+
+ // Just pass through target.
+ public void EndFigure()
+ {
+ _target.EndFigure();
+ }
+
+ // Just pass through target.
+ public void EndGeometry()
+ {
+ _target.EndGeometry();
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ private double GetUpdatedMeasure(double? m)
+ {
+ return m + _translateMeasure ?? _translateMeasure;
+ }
+ }
+}
diff --git a/src/Sinks/Geometry/Unprojector.cs b/src/Sinks/Geometry/Unprojector.cs
new file mode 100644
index 0000000..f186432
--- /dev/null
+++ b/src/Sinks/Geometry/Unprojector.cs
@@ -0,0 +1,59 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types.SQL;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ public sealed class UnProjector : IGeometrySink110
+ {
+ private readonly SqlProjection _projection;
+ private readonly IGeographySink110 _sink;
+
+ public UnProjector(SqlProjection projection, IGeographySink110 sink, int newSrid)
+ {
+ _projection = projection;
+ _sink = sink;
+ _sink.SetSrid(newSrid);
+ }
+
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ _sink.BeginGeography((OpenGisGeographyType)type);
+ }
+
+ public void EndGeometry()
+ {
+ _sink.EndGeography();
+ }
+
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _projection.UnprojectPoint(x, y, out double latitude, out double longitude);
+ _sink.BeginFigure(latitude, longitude, z, m);
+ }
+
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _projection.UnprojectPoint(x, y, out double latitude, out double longitude);
+ _sink.AddLine(latitude, longitude, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new System.Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void EndFigure()
+ {
+ _sink.EndFigure();
+ }
+
+ public void SetSrid(int srid)
+ {
+ // Input argument not used since a new srid is defined in the constructor.
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Sinks/Geometry/VacuousGeometryToGeographySink.cs b/src/Sinks/Geometry/VacuousGeometryToGeographySink.cs
new file mode 100644
index 0000000..978a76b
--- /dev/null
+++ b/src/Sinks/Geometry/VacuousGeometryToGeographySink.cs
@@ -0,0 +1,62 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.Sinks.Geometry
+{
+ ///
+ /// This class implements a completely trivial conversion from geometry to geography, simply taking each
+ /// point(x, y) --> (long, lat). The class takes a target geography sink, as well as the target SRID to
+ /// assign to the results.
+ ///
+ public class VacuousGeometryToGeographySink : IGeometrySink110
+ {
+ private readonly IGeographySink110 _target;
+ private readonly int _targetSrid;
+
+ public VacuousGeometryToGeographySink(int targetSrid, IGeographySink110 target)
+ {
+ _target = target;
+ _targetSrid = targetSrid;
+ }
+
+ public void AddLine(double x, double y, double? z, double? m)
+ {
+ _target.AddLine(y, x, z, m);
+ }
+
+ public void AddCircularArc(double x1, double y1, double? z1, double? m1, double x2, double y2, double? z2, double? m2)
+ {
+ throw new Exception("AddCircularArc is not implemented yet in this class");
+ }
+
+ public void BeginFigure(double x, double y, double? z, double? m)
+ {
+ _target.BeginFigure(y, x, z, m);
+ }
+
+ public void BeginGeometry(OpenGisGeometryType type)
+ {
+ // Convert geography to geometry types...
+ _target.BeginGeography((OpenGisGeographyType) type);
+ }
+
+ public void EndFigure()
+ {
+ _target.EndFigure();
+ }
+
+ public void EndGeometry()
+ {
+ _target.EndGeography();
+ }
+
+ public void SetSrid(int srid)
+ {
+ _target.SetSrid(_targetSrid);
+ }
+ }
+}
diff --git a/src/Types/LRSEnumerator.cs b/src/Types/LRSEnumerator.cs
new file mode 100644
index 0000000..b9c9b69
--- /dev/null
+++ b/src/Types/LRSEnumerator.cs
@@ -0,0 +1,57 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace SQLSpatialTools.Types
+{
+ ///
+ /// Enumerator for LRS Types
+ ///
+ ///
+ ///
+ internal class LRSEnumerator : IEnumerator
+ {
+ private readonly List _listOfItems;
+
+ // Enumerators are positioned before the first element
+ // until the first MoveNext() call.
+ private int _position = -1;
+
+ public LRSEnumerator(List list)
+ {
+ _listOfItems = list;
+ }
+
+ public bool MoveNext()
+ {
+ _position++;
+ return (_position < _listOfItems.Count);
+ }
+
+ public void Reset()
+ {
+ _position = -1;
+ }
+
+ object IEnumerator.Current => Current;
+
+ public T Current
+ {
+ get
+ {
+ try
+ {
+ return _listOfItems[_position];
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Types/LRSLine.cs b/src/Types/LRSLine.cs
new file mode 100644
index 0000000..d30720c
--- /dev/null
+++ b/src/Types/LRSLine.cs
@@ -0,0 +1,559 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Types
+{
+ ///
+ /// Data structure to capture LINESTRING geometry type.
+ ///
+ internal class LRSLine : IEnumerable
+ {
+ private List _points;
+ private string _wkt;
+ internal int SRID;
+ internal bool IsInRange;
+ internal bool IsCompletelyInRange;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The srid.
+ internal LRSLine(int srid)
+ {
+ SRID = srid;
+ _points = new List();
+ }
+
+ ///
+ /// Gets a value indicating whether this instance is empty or not.
+ ///
+ ///
+ /// true if this instance is multi line; otherwise, false.
+ ///
+ internal bool IsEmpty => !_points.Any() || _points.Count == 0;
+
+ ///
+ /// Gets a value indicating whether this instance has points.
+ ///
+ ///
+ /// true if this instance has points; otherwise, false.
+ ///
+ public bool HasPoints => _points != null && _points.Any();
+
+ ///
+ /// Gets the number of POINT in this LINESTRING.
+ ///
+ ///
+ /// The count.
+ ///
+ public int Count => _points.Any() ? _points.Count : 0;
+
+ ///
+ /// Determines whether this instance is LINESTRING.
+ ///
+ ///
+ /// true if this instance is line; otherwise, false.
+ ///
+ internal bool IsLine => _points.Any() && _points.Count > 1;
+
+ ///
+ /// Determines whether this instance is POINT.
+ ///
+ ///
+ /// true if this instance is point; otherwise, false.
+ ///
+ internal bool IsPoint => _points.Any() && _points.Count == 1;
+
+ ///
+ /// Gets the length.
+ ///
+ ///
+ /// The length.
+ ///
+ internal double Length { get; private set; }
+
+ #region Add Points
+
+ ///
+ /// Adds the point.
+ ///
+ /// The l rs point.
+ internal void AddPoint(LRSPoint lrsPoint)
+ {
+ UpdateLength(lrsPoint);
+ _points.Add(lrsPoint);
+ }
+
+ ///
+ /// Adds multiple points.
+ ///
+ /// List of lrs point.
+ internal void AddPoint(List lrsPoints)
+ {
+ lrsPoints.ForEach(AddPoint);
+ }
+
+ ///
+ /// Adds the point.
+ ///
+ /// The lrs points.
+ internal void AddPoint(params LRSPoint[] points)
+ {
+ foreach (var lrsPoint in points)
+ {
+ AddPoint(lrsPoint);
+ }
+ }
+
+ ///
+ /// Adds the point.
+ ///
+ /// The SqlGeometry point.
+ internal void AddPoint(SqlGeometry pointGeometry)
+ {
+ AddPoint(new LRSPoint(pointGeometry));
+ }
+
+ ///
+ /// Adds the point.
+ ///
+ /// The x.
+ /// The y.
+ /// The z.
+ /// The m.
+ internal void AddPoint(double x, double y, double? z, double? m)
+ {
+ AddPoint(new LRSPoint(x, y, z, m, SRID));
+ }
+
+ ///
+ /// Updates the length.
+ ///
+ /// The current point.
+ private void UpdateLength(LRSPoint currentPoint)
+ {
+ if (!_points.Any() || _points.Count <= 0) return;
+ var previousPoint = _points.Last();
+ Length += previousPoint.GetDistance(currentPoint);
+ }
+
+ #endregion
+
+ #region Point Manipulation
+
+ ///
+ /// Reverses the points of the line.
+ ///
+ internal void ReversePoints()
+ {
+ _points.Reverse();
+ }
+
+ ///
+ /// Gets the start point.
+ ///
+ ///
+ internal LRSPoint GetStartPoint()
+ {
+ return _points.Any() ? _points.First() : null;
+ }
+
+ ///
+ /// Gets the start point measure.
+ ///
+ ///
+ internal double GetStartPointM()
+ {
+ var currentPoint = GetStartPoint().M;
+ return currentPoint ?? 0;
+ }
+
+ ///
+ /// Gets the end point.
+ ///
+ ///
+ internal LRSPoint GetEndPoint()
+ {
+ return _points.Any() ? _points.Last() : null;
+ }
+
+ ///
+ /// Gets the end point m.
+ ///
+ ///
+ internal double GetEndPointM()
+ {
+ var currentPoint = GetEndPoint().M;
+ return currentPoint ?? 0;
+ }
+
+ ///
+ /// Locates the point.
+ ///
+ /// The measure.
+ ///
+ ///
+ internal LRSPoint LocatePoint(double measure, LRSPoint firstPoint = null)
+ {
+ var startPoint = firstPoint ?? GetStartPoint();
+ var endPoint = GetEndPoint();
+
+ if (startPoint.M == null || endPoint.M == null) return null;
+
+ var fraction = (measure - startPoint.M.Value) / (endPoint.M.Value - startPoint.M.Value);
+ var newX = (startPoint.X * (1 - fraction)) + (endPoint.X * fraction);
+ var newY = (startPoint.Y * (1 - fraction)) + (endPoint.Y * fraction);
+
+ return new LRSPoint(newX, newY, null, measure, SRID);
+ }
+
+ ///
+ /// Gets the point at m.
+ ///
+ ///
+ internal LRSPoint GetPointAtM(double measure)
+ {
+ return _points.FirstOrDefault(e => e.M.EqualsTo(measure));
+ }
+
+ ///
+ /// Scale the existing measure of geometry by multiplying existing measure with offsetMeasure
+ ///
+ ///
+ internal void ScaleMeasure(double offsetMeasure)
+ {
+ _points.ForEach(point => point.ScaleMeasure(offsetMeasure));
+ }
+
+ ///
+ /// Sum the existing measure with the offsetMeasure
+ ///
+ ///
+ internal void TranslateMeasure(double offsetMeasure)
+ {
+ _points.ForEach(point => point.TranslateMeasure(offsetMeasure));
+ }
+
+ ///
+ /// Removes the collinear points.
+ ///
+ internal void RemoveCollinearPoints()
+ {
+ // If the input segment has only two points; then there is no way of collinear
+ // so its no-op
+ if (_points.Count <= 2)
+ return;
+
+ var pointCounter = 0;
+ var pointsToRemove = new List();
+
+ foreach (var point in this)
+ {
+ // ignore first point and last point
+ if (pointCounter == 0 || pointCounter == _points.Count - 1)
+ {
+ pointCounter++;
+ continue;
+ }
+
+ // previous point is A
+ var previousPoint = _points[pointCounter - 1];
+ // slope AB
+ var slopeAB = previousPoint.Slope;
+ var slopeABType = previousPoint.SlopeType;
+
+ // current point is B, slope is BC
+ var slopeBC = point.Slope;
+ var slopeBCType = point.SlopeType;
+
+ // next point is C
+ var nextPoint = _points[pointCounter + 1];
+ // calculate AC slope
+ var slopeAC = previousPoint.GetSlope(nextPoint, out var slopeACType);
+
+ // for each point previous and next point slope is compared
+ // if slope of AB = BC = CA
+ var isABBCSlopeEqual = slopeAB.HasValue && slopeBC.HasValue
+ ? slopeAB.Value.EqualsTo(slopeBC.Value)
+ : slopeABType == slopeBCType;
+ var isBCACSlopeEqual = slopeBC.HasValue && slopeAC.HasValue
+ ? slopeBC.Value.EqualsTo(slopeAC.Value)
+ : slopeBCType == slopeACType;
+
+ if (isABBCSlopeEqual && isBCACSlopeEqual)
+ pointsToRemove.Add(point);
+ pointCounter++;
+ }
+
+ // remove the collinear points
+ RemovePoints(pointsToRemove);
+ }
+
+ ///
+ /// Removes the points.
+ ///
+ /// The points to remove.
+ internal void RemovePoints(List pointsToRemove)
+ {
+ pointsToRemove.ForEach(pointToRemove => _points.Remove(pointToRemove));
+ }
+
+ ///
+ /// Calculates the slope.
+ ///
+ internal void CalculateSlope()
+ {
+ var pointIterator = 1;
+ foreach (var point in _points)
+ {
+ // last point; next point is the first point
+ point.CalculateSlope(pointIterator == _points.Count ? _points.First() : _points[pointIterator]);
+
+ pointIterator++;
+ }
+ }
+
+ #endregion
+
+ #region Data Structure Conversions
+
+ ///
+ /// Converts to WKT format.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ public override string ToString()
+ {
+ if (IsEmpty)
+ {
+ _wkt = "LINESTRING EMPTY";
+ return _wkt;
+ }
+
+ var wktBuilder = new StringBuilder();
+
+ if (IsLine)
+ wktBuilder.Append("LINESTRING (");
+ else if (IsPoint)
+ wktBuilder.Append("POINT (");
+
+ var pointIterator = 1;
+
+ foreach (var point in _points)
+ {
+ wktBuilder.AppendFormat("{0} {1} {2}", point.X, point.Y, point.M);
+ if (pointIterator != _points.Count)
+ wktBuilder.Append(", ");
+ pointIterator++;
+ }
+ wktBuilder.Append(")");
+ _wkt = wktBuilder.ToString();
+
+ return _wkt;
+ }
+
+ ///
+ /// Converts to SqlGeometry.
+ ///
+ ///
+ internal SqlGeometry ToSqlGeometry()
+ {
+ var geomBuilder = new SqlGeometryBuilder();
+ return ToSqlGeometry(ref geomBuilder);
+ }
+
+ ///
+ /// Method returns the SqlGeometry form of the MULTILINESTRING
+ ///
+ /// Reference SqlGeometryBuilder to be used for building Geometry.
+ /// SqlGeometry
+ internal SqlGeometry ToSqlGeometry(ref SqlGeometryBuilder geomBuilder)
+ {
+ // ignore if the line has only one point.
+ if (_points.Count < 2)
+ return GetStartPoint().ToSqlGeometry(ref geomBuilder);
+ BuildSqlGeometry(ref geomBuilder);
+ return geomBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Builds the SqlGeometry form LINESTRING or POINT
+ ///
+ /// The geom builder.
+ ///
+ internal void BuildSqlGeometry(ref SqlGeometryBuilder geomBuilder, bool isFromMultiLine = false)
+ {
+ if (!isFromMultiLine)
+ geomBuilder.SetSrid(SRID);
+ geomBuilder.BeginGeometry(OpenGisGeometryType.LineString);
+ var pointIterator = 1;
+ foreach (var point in _points)
+ {
+ if (pointIterator == 1)
+ geomBuilder.BeginFigure(point.X, point.Y, point.Z, point.M);
+ else
+ geomBuilder.AddLine(point.X, point.Y, point.Z, point.M);
+ pointIterator++;
+ }
+ geomBuilder.EndFigure();
+ geomBuilder.EndGeometry();
+ }
+
+ #endregion
+
+ #region Modules for Offset Angle Calculation
+ ///
+ /// Calculate offset bearings for all points.
+ ///
+ private void CalculateOffsetBearing()
+ {
+ var pointCount = _points.Count;
+ for (var i = 0; i < pointCount; i++)
+ {
+ var currentPoint = _points[i];
+ if (i != pointCount - 1)
+ {
+ var nextPoint = _points[i + 1];
+ currentPoint.SetOffsetBearing(nextPoint);
+ }
+ else
+ {
+ currentPoint.OffsetBearing = null;
+ }
+ }
+ }
+
+ ///
+ /// Calculate offset angle between points
+ ///
+ ///
+ private void CalculateOffsetAngle(bool isNegativeOffset)
+ {
+ var pointCount = _points.Count;
+ for (var i = 0; i < pointCount; i++)
+ {
+ var currentPoint = _points[i];
+ // first point
+ if (i == 0)
+ {
+ _points[i].SetOffsetAngle(null, isNegativeOffset);
+ }
+ // except first point
+ else
+ {
+ var previousPoint = _points[i - 1];
+ currentPoint.SetOffsetAngle(previousPoint, isNegativeOffset);
+ }
+ }
+ }
+
+ ///
+ /// Calculate offset distance between points
+ ///
+ /// Offset value
+ private void CalculateOffset(double offset)
+ {
+ var pointCount = _points.Count;
+ for (var i = 0; i < pointCount; i++)
+ {
+ var currentPoint = _points[i];
+ // if not last point
+ if (i != pointCount - 1)
+ {
+ // other points point
+ currentPoint.SetOffsetDistance(offset);
+ }
+ else
+ {
+ // for the last point specified offset is offset distance.
+ // if decrease offset distance should be negative
+ currentPoint.OffsetDistance = Math.Abs(offset);
+ }
+ }
+ }
+
+ ///
+ /// Compute a parallel line to input line segment
+ ///
+ /// Offset Value
+ /// The tolerance.
+ /// Parallel Line
+ internal LRSLine ComputeParallelLine(double offset, double tolerance)
+ {
+ CalculateOffsetBearing();
+ CalculateOffsetAngle(offset < 0);
+ CalculateOffset(offset);
+
+ var parallelLine = new LRSLine(SRID);
+
+ // compute parallel point and add additional vertices
+ foreach (var point in _points)
+ {
+ parallelLine.AddPoint(point.GetAndPopulateParallelPoints(offset, tolerance, ref _points));
+ }
+
+ return parallelLine;
+ }
+
+ ///
+ /// Populates the measures.
+ ///
+ /// Length of the segment.
+ /// Length of the current.
+ /// The start measure.
+ /// The end measure.
+ ///
+ internal void PopulateMeasures(double segmentLength, ref double currentLength, double startMeasure, double endMeasure)
+ {
+ var pointCount = _points.Count;
+ for (var i = 0; i < pointCount; i++)
+ {
+ if (i == pointCount - 1)
+ _points[i].M = GetEndPointM();
+ else if (i == 0)
+ _points[i].M = GetStartPointM();
+ else
+ {
+ var currentPoint = _points[i];
+ currentPoint.ReCalculateMeasure(_points[i - 1], ref currentLength, segmentLength, startMeasure, endMeasure);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Enumeration
+
+ ///
+ /// Returns an enumerator that iterates through each POINT in a LINESTRING.
+ ///
+ ///
+ /// An object that can be used to iterate through the collection.
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// Gets the enumerator.
+ ///
+ ///
+ public LRSEnumerator GetEnumerator()
+ {
+ return new LRSEnumerator(_points);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Types/LRSMultiLine.cs b/src/Types/LRSMultiLine.cs
new file mode 100644
index 0000000..29132dc
--- /dev/null
+++ b/src/Types/LRSMultiLine.cs
@@ -0,0 +1,419 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.Types
+{
+ ///
+ /// Data structure to capture MULTILINESTRING geometry type.
+ ///
+ internal class LRSMultiLine : IEnumerable
+ {
+ private readonly List _lines;
+ internal readonly int SRID;
+ private string _wkt;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The srid.
+ internal LRSMultiLine(int srid)
+ {
+ SRID = srid;
+ _lines = new List();
+ }
+
+ ///
+ /// Gets a value indicating whether this instance is empty or not.
+ ///
+ ///
+ /// true if this instance is multi line; otherwise, false.
+ ///
+ internal bool IsEmpty => !_lines.Any() || _lines.Count == 0;
+
+ ///
+ /// Gets the number of line segments in the MULTILINESTRING.
+ ///
+ ///
+ /// The count.
+ ///
+ internal int Count => _lines.Any() ? _lines.Count : 0;
+
+ ///
+ /// Gets a value indicating whether this instance is MULTILINESTRING.
+ ///
+ ///
+ /// true if this instance is a multi line; otherwise, false.
+ ///
+ internal bool IsMultiLine => Count > 1;
+
+ ///
+ /// Gets a value indicating whether this instance is LINESTRING.
+ ///
+ ///
+ /// true if this instance is a line string; otherwise, false.
+ ///
+ internal bool IsLine => Count == 1;
+
+ ///
+ /// Gets a value indicating whether this instance is a 2 POINT LINESTRING.
+ ///
+ ///
+ /// true if this instance is a 2 point line string; otherwise, false.
+ ///
+ internal bool Is2PointLine => IsLine && GetFirstLine().Count == 2;
+
+ ///
+ /// Gets the length.
+ ///
+ ///
+ /// The length.
+ ///
+ internal double Length { get { return _lines.Any() ? _lines.Sum(line => line.Length) : 0; } }
+
+ #region Add Lines
+
+ ///
+ /// Adds the line.
+ ///
+ /// The LRS line.
+ internal void AddLine(LRSLine line)
+ {
+ if (line != null && line.HasPoints && line.IsLine)
+ _lines.Add(line);
+ }
+
+ ///
+ /// Adds multiple lines.
+ ///
+ /// The line list.
+ internal void AddLines(List lineList)
+ {
+ if (lineList != null && lineList.Any())
+ lineList.ForEach(AddLine);
+ }
+
+ ///
+ /// Adds LRS Multi lines.
+ ///
+ /// The line list.
+ internal void Add(LRSMultiLine lrsMultiLine)
+ {
+ if (lrsMultiLine?._lines != null && lrsMultiLine._lines.Any())
+ AddLines(lrsMultiLine._lines);
+ }
+
+ #endregion
+
+ #region Line Manipulation
+
+ ///
+ /// Scale the existing measure of geometry by multiplying existing measure with offsetMeasure
+ ///
+ ///
+ internal void ScaleMeasure(double offsetMeasure)
+ {
+ _lines.ForEach(line => line.ScaleMeasure(offsetMeasure));
+ }
+
+ ///
+ /// Sum the existing measure with the offsetMeasure
+ ///
+ ///
+ internal void TranslateMeasure(double offsetMeasure)
+ {
+ _lines.ForEach(line => line.TranslateMeasure(offsetMeasure));
+ }
+
+ ///
+ /// Reverses only the LINESTRING segments order in a MULTILINESTRING
+ ///
+ internal void ReversLines()
+ {
+ _lines.Reverse();
+ }
+
+ ///
+ /// Removes the collinear points.
+ ///
+ internal void RemoveCollinearPoints()
+ {
+ // First calculate the slope to remove collinear points.
+ CalculateSlope();
+ _lines.ForEach(line => line.RemoveCollinearPoints());
+ }
+
+ ///
+ /// Calculates the slope.
+ ///
+ internal void CalculateSlope()
+ {
+ _lines.ForEach(line => line.CalculateSlope());
+ }
+
+ ///
+ /// Computes the offset.
+ ///
+ /// The offset.
+ /// The tolerance.
+ ///
+ internal LRSMultiLine ComputeOffset(double offset, double tolerance)
+ {
+ var parallelMultiLine = new LRSMultiLine(SRID);
+ _lines.ForEach(line => parallelMultiLine._lines.Add(line.ComputeParallelLine(offset, tolerance)));
+ return parallelMultiLine;
+ }
+
+ ///
+ /// Populates the measures.
+ ///
+ /// The start m.
+ /// The end m.
+ internal void PopulateMeasures(double? startM, double? endM)
+ {
+ var startMeasure = startM ?? 0;
+ var endMeasure = endM ?? Length;
+ double currentLength = 0;
+
+ _lines.ForEach(line =>
+ {
+ line.PopulateMeasures(Length, ref currentLength, startMeasure, endMeasure);
+ });
+
+ }
+
+ ///
+ /// Reverse both LINESTRING segment and its POINTS
+ ///
+ internal void ReverseLinesAndPoints()
+ {
+ ReversLines();
+ _lines.ForEach(line => line.ReversePoints());
+ }
+
+ ///
+ /// Gets the first LINESTRING in a MULTILINESTRING
+ ///
+ ///
+ internal LRSLine GetFirstLine()
+ {
+ return _lines.Any() ? _lines.First() : null;
+ }
+
+ ///
+ /// Gets the last LINESTRING in a MULTILINESTRING.
+ ///
+ ///
+ internal LRSLine GetLastLine()
+ {
+ return _lines.Any() ? _lines.Last() : null;
+ }
+
+ ///
+ /// Gets the start POINT in a MULTILINESTRING
+ ///
+ ///
+ internal LRSPoint GetStartPoint()
+ {
+ return _lines.Any() ? _lines.First().GetStartPoint() : null;
+ }
+
+ ///
+ /// Gets the start POINT measure in a MULTILINESTRING
+ ///
+ ///
+ internal double? GetStartPointM()
+ {
+ return GetStartPoint()?.M;
+ }
+
+ ///
+ /// Gets the end POINT in a MULTILINESTRING
+ ///
+ ///
+ internal LRSPoint GetEndPoint()
+ {
+ return _lines.Any() ? _lines.Last().GetEndPoint() : null;
+ }
+
+ ///
+ /// Gets the end POINT measure in a MULTILINESTRING
+ ///
+ ///
+ internal double? GetEndPointM()
+ {
+ return GetEndPoint()?.M;
+ }
+
+ ///
+ /// Gets the POINT in a MULTILINESTRING at a specified measure
+ ///
+ ///
+ internal LRSPoint GetPointAtM(double measure)
+ {
+ if (!_lines.Any()) return null;
+
+ foreach (var line in _lines)
+ {
+ var point = line.GetPointAtM(measure);
+ if (point != null)
+ return point;
+ }
+ return null;
+ }
+
+ ///
+ /// Removes the first.
+ ///
+ ///
+ internal void RemoveFirst()
+ {
+ if (!_lines.Any()) return;
+ _lines.RemoveAt(0);
+ }
+
+ ///
+ /// Removes the last.
+ ///
+ ///
+ internal void RemoveLast()
+ {
+ if (!_lines.Any()) return;
+ _lines.RemoveAt(_lines.Count - 1);
+ }
+
+ #endregion
+
+ #region Data Structure Conversion
+
+ ///
+ /// Converts to WKT format.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ public override string ToString()
+ {
+ if (IsEmpty)
+ {
+ _wkt = string.Empty;
+ return "MULTILINESTRING EMPTY";
+ }
+
+ if(IsLine)
+ return GetFirstLine().ToString();
+
+ var wktBuilder = new StringBuilder();
+ if (IsMultiLine)
+ wktBuilder.Append("MULTILINESTRING (");
+
+ var lineIterator = 1;
+
+ foreach (var line in _lines)
+ {
+ if (line.IsLine)
+ wktBuilder.Append(line.ToString().Replace("LINESTRING ", string.Empty));
+
+ if (lineIterator != _lines.Count)
+ wktBuilder.Append(", ");
+ lineIterator++;
+ }
+
+ if (IsMultiLine)
+ wktBuilder.Append(")");
+
+ _wkt = wktBuilder.ToString();
+
+ return _wkt;
+ }
+
+ ///
+ /// Method returns the SqlGeometry form of the MULTILINESTRING
+ ///
+ /// SqlGeometry
+ internal SqlGeometry ToSqlGeometry()
+ {
+ var geomBuilder = new SqlGeometryBuilder();
+ return ToSqlGeometry(ref geomBuilder);
+ }
+
+ ///
+ /// Method returns the SqlGeometry form of the MULTILINESTRING
+ ///
+ /// Reference SqlGeometryBuilder to be used for building Geometry.
+ /// SqlGeometry
+ internal SqlGeometry ToSqlGeometry(ref SqlGeometryBuilder geomBuilder)
+ {
+ if (IsEmpty)
+ return SqlGeometry.Null;
+
+ return BuildSqlGeometry(ref geomBuilder) ? geomBuilder.ConstructedGeometry : null;
+ }
+
+ ///
+ /// Method builds the SqlGeometry form of the MULTILINESTRING through reference GeometryBuilder.
+ ///
+ /// Reference SqlGeometryBuilder to be used for building Geometry.
+ internal bool BuildSqlGeometry(ref SqlGeometryBuilder geomBuilder)
+ {
+ var isBuildDone = false;
+ if (IsEmpty)
+ return false;
+
+ if (geomBuilder == null)
+ geomBuilder = new SqlGeometryBuilder();
+
+ if (IsMultiLine)
+ {
+ geomBuilder.SetSrid(SRID);
+ geomBuilder.BeginGeometry(OpenGisGeometryType.MultiLineString);
+ isBuildDone = true;
+ }
+
+ // ignore points
+ foreach (var line in _lines.Where(line => line.IsLine).ToList())
+ {
+ line.BuildSqlGeometry(ref geomBuilder, IsMultiLine);
+ isBuildDone = true;
+ }
+
+ if (IsMultiLine)
+ geomBuilder.EndGeometry();
+
+ return isBuildDone;
+ }
+
+
+ #endregion
+
+ #region Enumeration
+
+ ///
+ /// Gets the enumerator.
+ ///
+ ///
+ public LRSEnumerator GetEnumerator()
+ {
+ return new LRSEnumerator(_lines);
+ }
+
+ ///
+ /// Returns an enumerator that iterates through each LINESTRING in a MULTILINESTRING.
+ ///
+ ///
+ /// An object that can be used to iterate through the collection.
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Types/LRSPoint.cs b/src/Types/LRSPoint.cs
new file mode 100644
index 0000000..1068e01
--- /dev/null
+++ b/src/Types/LRSPoint.cs
@@ -0,0 +1,606 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Types
+{
+ ///
+ /// Data structure to capture POINT geometry type.
+ ///
+ internal class LRSPoint
+ {
+ // Fields.
+ private string _wkt;
+ internal readonly double X;
+ internal readonly double Y;
+ internal double? Z, M;
+ private readonly int _srid;
+ internal double? Slope;
+ private double _angle;
+ internal SlopeValue SlopeType;
+
+ internal double? OffsetBearing;
+ private double _offsetAngle;
+ internal double OffsetDistance;
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The x.
+ /// The y.
+ /// The z.
+ /// The m.
+ /// The srid.
+ internal LRSPoint(double x, double y, double? z, double? m, int srid)
+ {
+ X = x;
+ Y = y;
+ Z = m.HasValue ? z : null;
+ M = m ?? z;
+ _srid = srid;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The SQL geometry.
+ internal LRSPoint(SqlGeometry sqlGeometry)
+ {
+ if (sqlGeometry.IsNullOrEmpty() || !sqlGeometry.IsPoint())
+ return;
+
+ X = sqlGeometry.STX.Value;
+ Y = sqlGeometry.STY.Value;
+ Z = sqlGeometry.HasZ ? sqlGeometry.Z.Value : (double?)null;
+ M = sqlGeometry.HasM ? sqlGeometry.M.Value : (double?)null;
+ _srid = (int)sqlGeometry.STSrid;
+ }
+
+ #endregion
+
+ #region Point Manipulation
+
+ ///
+ /// Translating measure of LRSPoint
+ ///
+ ///
+ internal void TranslateMeasure(double offsetMeasure)
+ {
+ M += offsetMeasure;
+ }
+
+ ///
+ /// Scaling Measure of LRSPoint
+ ///
+ ///
+ internal void ScaleMeasure(double offsetMeasure)
+ {
+ M *= offsetMeasure;
+ }
+
+ ///
+ /// Gets the distance from point.
+ ///
+ /// The next point.
+ /// Offset Point.
+ internal double GetDistance(LRSPoint nextPoint)
+ {
+ return SpatialExtensions.GetDistance(X, Y, nextPoint.X, nextPoint.Y);
+ }
+
+ ///
+ /// Determines whether X, Y co-ordinates of current and second point is within tolerance
+ ///
+ /// The second point.
+ /// The tolerance.
+ ///
+ /// true if X, Y co-ordinates of current and second point is within tolerance; otherwise, false.
+ ///
+ internal bool IsXYWithinTolerance(LRSPoint secondPoint, double tolerance)
+ {
+ return (Math.Abs(X - secondPoint.X) <= tolerance && Math.Abs(Y - secondPoint.Y) <= tolerance);
+ }
+
+ ///
+ /// Re calculate the measure.
+ ///
+ /// The previous point.
+ ///
+ /// The total length.
+ /// The start measure.
+ /// The end measure.
+ internal void ReCalculateMeasure(LRSPoint previousPoint, ref double currentLength, double totalLength, double startMeasure, double endMeasure)
+ {
+ currentLength += GetDistance(previousPoint);
+ M = startMeasure + (currentLength / totalLength) * (endMeasure - startMeasure);
+ }
+
+ ///
+ /// Gets the previous point.
+ ///
+ /// The points.
+ /// The current point.
+ ///
+ internal static LRSPoint GetPreviousPoint(ref List points, LRSPoint currentPoint)
+ {
+ var index = points.IndexOf(currentPoint);
+ // return null if index is -1; null for first point
+ return index > 0 ? points[index - 1] : null;
+ }
+
+ ///
+ /// Gets the next point.
+ ///
+ /// The points.
+ /// The current point.
+ ///
+ internal static LRSPoint GetNextPoint(ref List points, LRSPoint currentPoint)
+ {
+ var index = points.IndexOf(currentPoint);
+ // return null if index is -1 or last index
+ return index >= 0 && index < points.Count - 1 ? points[index + 1] : null;
+ }
+
+ #endregion
+
+ #region Operator Overloading
+
+ ///
+ /// Implements the operator -.
+ ///
+ /// LRS Point 1.
+ /// LRS Point 2.
+ /// The result of the operator.
+ public static LRSPoint operator -(LRSPoint a, LRSPoint b)
+ {
+ return new LRSPoint(b.X - a.X, b.Y - a.Y, null, null, a._srid);
+ }
+
+ ///
+ /// Implements the operator ==.
+ ///
+ /// LRS Point 1.
+ /// LRS Point 2.
+ /// The result of the operator.
+
+ public static bool operator ==(LRSPoint a, LRSPoint b)
+ {
+ if (ReferenceEquals(b, null) && ReferenceEquals(a, null))
+ return true;
+ if (ReferenceEquals(b, null) || ReferenceEquals(a, null))
+ return false;
+ return a.X.EqualsTo(b.X) && a.Y.EqualsTo(b.Y) && EqualityComparer.Default.Equals(a.M, b.M);
+ }
+
+ ///
+ /// Implements the operator !=.
+ ///
+ /// LRS Point 1.
+ /// LRS Point 2.
+ /// The result of the operator.
+ public static bool operator !=(LRSPoint a, LRSPoint b)
+ {
+ return !(a == b);
+ }
+
+ ///
+ /// Determines whether the specified , is equal to this instance.
+ ///
+ /// The to compare with this instance.
+ ///
+ /// true if the specified is equal to this instance; otherwise, false.
+ ///
+ public override bool Equals(object obj)
+ {
+ var point = obj as LRSPoint;
+ return point != null &&
+ X.EqualsTo(point.X) &&
+ Y.EqualsTo(point.Y) &&
+ EqualityComparer.Default.Equals(M, point.M);
+ }
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ // ReSharper disable NonReadonlyMemberInGetHashCode
+ public override int GetHashCode()
+ {
+ var hashCode = 1911090832;
+ hashCode = hashCode * 1521134295 + X.GetHashCode();
+ hashCode = hashCode * 1521134295 + Y.GetHashCode();
+ hashCode = hashCode * 1521134295 + EqualityComparer.Default.GetHashCode(Z);
+ hashCode = hashCode * 1521134295 + EqualityComparer.Default.GetHashCode(M);
+ return hashCode;
+ }
+ // ReSharper restore NonReadonlyMemberInGetHashCode
+
+ #endregion
+
+ #region Data Structure Conversions
+
+ ///
+ /// Converts to WKT format.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ public override string ToString()
+ {
+ _wkt = $"POINT ({X} {Y} {M})";
+
+ return _wkt;
+ }
+
+ ///
+ /// Converts to SqlGeometry.
+ ///
+ ///
+ internal SqlGeometry ToSqlGeometry()
+ {
+ var geomBuilder = new SqlGeometryBuilder();
+ return ToSqlGeometry(ref geomBuilder);
+ }
+
+ ///
+ /// Converts to SqlGeometry.
+ ///
+ /// The geometry builder.
+ ///
+ internal SqlGeometry ToSqlGeometry(ref SqlGeometryBuilder geometryBuilder)
+ {
+ geometryBuilder.SetSrid(_srid);
+ geometryBuilder.BeginGeometry(OpenGisGeometryType.Point);
+ geometryBuilder.BeginFigure(X, Y, Z, M);
+ geometryBuilder.EndFigure();
+ geometryBuilder.EndGeometry();
+ return geometryBuilder.ConstructedGeometry;
+ }
+
+ #endregion
+
+ #region Parallel Point Computation
+
+ ///
+ /// Gets the offset point.
+ ///
+ /// The next point.
+ /// Offset Point.
+ private LRSPoint GetOffsetPoint(LRSPoint nextPoint)
+ {
+ return this - nextPoint;
+ }
+
+ ///
+ /// Gets the arc to tangent.
+ ///
+ /// The next point.
+ /// In Radian
+ private double GetAtanInRadian(LRSPoint nextPoint)
+ {
+ var offsetPoint = GetOffsetPoint(nextPoint);
+ return Math.Atan2(offsetPoint.Y, offsetPoint.X);
+ }
+
+ ///
+ /// Gets the atan2 in degrees.
+ /// This does angle correct when atan2 value is negative
+ ///
+ /// The point1.
+ /// The point2.
+ /// Atan2 in degrees
+ private double GetAtanInDegree(LRSPoint point1, LRSPoint point2)
+ {
+ var atan = point1.GetAtanInRadian(point2);
+ return SpatialUtil.ToDegrees(atan <= 0 ? (2 * Math.PI) + atan : atan);
+ }
+
+ ///
+ /// Gets the first point radian.
+ ///
+ /// The previous point.
+ /// The middle point.
+ ///
+ private double GetFirstPointRadian(LRSPoint previousPoint, LRSPoint middlePoint)
+ {
+ var atan = GetAtanInDegree(middlePoint, previousPoint);
+ atan = 90 - atan;
+ atan = atan <= 0 ? 360 + atan : atan;
+
+ return SpatialUtil.ToRadians(360 - atan);
+ }
+
+ ///
+ /// Gets the second point radian.
+ ///
+ /// The next point.
+ /// The middle point.
+ ///
+ private double GetSecondPointRadian(LRSPoint nextPoint, LRSPoint middlePoint)
+ {
+ var atan = GetAtanInDegree(middlePoint, nextPoint);
+ atan = 90 - atan;
+ atan = atan <= 0 ? 360 + atan : atan;
+
+ return SpatialUtil.ToRadians(180 - atan);
+ }
+
+ ///
+ /// Gets the deviation angle of 3 points.
+ ///
+ /// The point a.
+ /// The point o.
+ /// The point b.
+ /// if set to true [is negative offset].
+ ///
+ private double GetAOBAngle(LRSPoint pointA, LRSPoint pointO, LRSPoint pointB, bool isNegativeOffset)
+ {
+ const double angleCorrection = Math.PI / 2;
+ const double angleConversion = 2 * Math.PI;
+
+ var atanAo = pointA.GetAtanInRadian(pointO) + angleCorrection;
+ var atanBo = pointB.GetAtanInRadian(pointO) + angleCorrection;
+
+ // angle conversion
+ atanAo = SpatialUtil.ToDegrees(atanAo <= 0 ? angleConversion + atanAo : atanAo);
+ atanBo = SpatialUtil.ToDegrees(atanBo <= 0 ? angleConversion + atanBo : atanBo);
+
+ var deviationAngle =
+ 360 - (atanAo > atanBo
+ ? 360 - (atanAo - atanBo)
+ : atanBo - atanAo);
+
+ // for positive offset; offset curve will be to the left of input geom;
+ // so for positive deviation angle the computed angle should be subtracted from 360
+ // for negative offset; offset curve will be to the right of input geom
+ return isNegativeOffset ? deviationAngle : 360 - deviationAngle;
+ }
+
+ ///
+ /// Sets the offset bearing.
+ ///
+ /// The next point.
+ internal void SetOffsetBearing(LRSPoint nextPoint)
+ {
+ if (nextPoint != null)
+ OffsetBearing = CalculateOffsetBearing(nextPoint);
+ }
+
+ ///
+ /// Calculates the offset bearing.
+ ///
+ /// The next point.
+ private double CalculateOffsetBearing(LRSPoint nextPoint)
+ {
+ _angle = SpatialUtil.ToDegrees(GetAtanInRadian(nextPoint));
+ return (90 - _angle + 360) % 360;
+ }
+
+ ///
+ /// Sets the offset angle.
+ ///
+ /// The current point.
+ /// Is Offset is Negative
+ internal void SetOffsetAngle(LRSPoint previousPoint, bool isNegativeOffset)
+ {
+ _offsetAngle = CalculateOffsetAngle(previousPoint, isNegativeOffset);
+ }
+
+ ///
+ /// Calculates the offset angle.
+ ///
+ /// The current point.
+ /// Is Offset is Negative
+ private double CalculateOffsetAngle(LRSPoint previousPoint, bool isNegativeOffset)
+ {
+ double offsetAngle = 0;
+
+ var previousPointOffsetBearing = previousPoint?.OffsetBearing;
+
+ // Left
+ if (!isNegativeOffset)
+ {
+ if (OffsetBearing == null)
+ {
+ if (previousPointOffsetBearing != null) offsetAngle = (double)previousPointOffsetBearing - 90;
+ }
+ else if (previousPointOffsetBearing == null)
+ offsetAngle = (double)OffsetBearing - 90;
+ else
+ //(360 + b1.OffsetBearing - ((360 - ((b2.OffsetBearing + 180) - b1.OffsetBearing)) / 2)) % 360
+ offsetAngle = (
+ 360 + (double)OffsetBearing -
+ (
+ (
+ 360 - (
+ ((double)previousPointOffsetBearing + 180) - (double)OffsetBearing
+ )
+ ) / 2
+ )
+ ) % 360;
+ }
+ // Right
+ else
+ {
+
+ if (OffsetBearing == null)
+ {
+ if (previousPointOffsetBearing != null) offsetAngle = (double)previousPointOffsetBearing + 90;
+ }
+ else if (previousPointOffsetBearing == null)
+ offsetAngle = (double)OffsetBearing + 90;
+ else
+ // (b1.OffsetBearing + ((((b2.OffsetBearing + 180) - b1.OffsetBearing)) / 2)) % 360
+ offsetAngle = ((double)OffsetBearing + (((((double)previousPointOffsetBearing + 180) - (double)OffsetBearing)) / 2)) % 360;
+ }
+
+ return offsetAngle;
+ }
+
+ ///
+ /// Sets the offset distance.
+ ///
+ /// The offset.
+ internal void SetOffsetDistance(double offset)
+ {
+ var offsetBearing = OffsetBearing ?? default(double);
+ // offset / (SIN(RADIANS(((OffsetBearing - OffsetAngleLeft) + 360) % 360)))
+ OffsetDistance = CalculateOffsetDistance(offset, offsetBearing, _offsetAngle);
+ }
+
+ ///
+ /// Calculates the offset distance.
+ ///
+ /// The offset.
+ ///
+ ///
+ private static double CalculateOffsetDistance(double offset, double offsetBearing, double offsetAngle)
+ {
+ // offset / (SIN(RADIANS(((OffsetBearing - OffsetAngleLeft) + 360) % 360)))
+ var denominator = (Math.Sin(SpatialUtil.ToRadians(((offsetBearing - offsetAngle) + 360) % 360)));
+ return offset / denominator;
+ }
+
+ ///
+ /// Gets the parallel point.
+ ///
+ /// Point parallel to the current point.
+ private LRSPoint GetParallelPoint()
+ {
+ var newX = X + (OffsetDistance * Math.Cos(SpatialUtil.ToRadians(90 - _offsetAngle)));
+ var newY = Y + (OffsetDistance * Math.Sin(SpatialUtil.ToRadians(90 - _offsetAngle)));
+
+ return new LRSPoint(
+ newX,
+ newY,
+ null,
+ M,
+ _srid
+ );
+ }
+
+ ///
+ /// Compute and populate parallel points on bend lines.
+ ///
+ /// The offset.
+ /// The points.
+ ///
+ /// Point parallel to the current point.
+ internal List GetAndPopulateParallelPoints(double offset, double tolerance, ref List points)
+ {
+ // list to capture additional vertices.
+ var lrsPoints = new List();
+
+ // parallel point to the current point
+ var parallelPoint = GetParallelPoint();
+
+ // get previous and next point
+ var previousPoint = GetPreviousPoint(ref points, this);
+ var nextPoint = GetNextPoint(ref points, this);
+
+ // offset distance between parallel point and input point
+ var diffInDistance = Math.Round(parallelPoint.GetDistance(this), 5);
+ // offset distance difference between parallel point and input point
+ var offsetDiff = Math.Abs(Math.Abs(diffInDistance) - Math.Abs(offset));
+
+ if (offsetDiff <= tolerance || previousPoint == null || nextPoint == null)
+ {
+ lrsPoints.Add(parallelPoint);
+ }
+ else
+ {
+ var negativeOffset = offset < 0;
+ var deviationAngle = GetAOBAngle(previousPoint, this, nextPoint, negativeOffset);
+
+ if (deviationAngle <= 90)
+ {
+ var firsPointRadian = GetFirstPointRadian(previousPoint, this);
+ var nextPointRadian = GetSecondPointRadian(nextPoint, this);
+
+ // first point
+ var firstPointX = X + (offset * Math.Cos(firsPointRadian));
+ var firstPointY = Y + (offset * Math.Sin(firsPointRadian));
+ var firstPoint = new LRSPoint(firstPointX, firstPointY, null, M, _srid);
+
+ // second point
+ var secondPointX = X + (offset * Math.Cos(nextPointRadian));
+ var secondPointY = Y + (offset * Math.Sin(nextPointRadian));
+ var secondPoint = new LRSPoint(secondPointX, secondPointY, null, M, _srid);
+
+ // if computed first point is within tolerance of second point then add only first point
+ if (firstPoint.GetDistance(secondPoint) <= tolerance)
+ {
+ lrsPoints.Add(firstPoint);
+ }
+ else
+ {
+ // add first point
+ lrsPoints.Add(firstPoint);
+
+ // compute middle point
+ var fraction = Math.Abs(offset / OffsetDistance);
+ var middleX = (X * (1 - fraction)) + (parallelPoint.X * fraction);
+ var middleY = (Y * (1 - fraction)) + (parallelPoint.Y * fraction);
+ var middlePoint = new LRSPoint(middleX, middleY, null, M, _srid);
+
+ // if not within tolerance add middle point
+ if (firstPoint.GetDistance(middlePoint) > tolerance)
+ lrsPoints.Add(middlePoint);
+
+ // add second point
+ lrsPoints.Add(secondPoint);
+ }
+ }
+ else
+ lrsPoints.Add(parallelPoint);
+ }
+
+ return lrsPoints;
+ }
+
+ ///
+ /// Calculates the slope.
+ ///
+ /// The next LRS point.
+ internal void CalculateSlope(LRSPoint nextLRSPoint)
+ {
+ Slope = GetSlope(nextLRSPoint, out SlopeType);
+ }
+
+ ///
+ /// Calculates the slope.
+ ///
+ /// The next LRS point.
+ ///
+ internal double? GetSlope(LRSPoint nextLRSPoint, out SlopeValue slopeValue)
+ {
+ slopeValue = SlopeValue.None;
+ var xDifference = nextLRSPoint.X - X;
+ var yDifference = nextLRSPoint.Y - Y;
+
+ if (xDifference.EqualsTo(0))
+ {
+ slopeValue = yDifference > 0 ? SlopeValue.PositiveInfinity : SlopeValue.NegativeInfinity;
+ return null;
+ }
+ else if (yDifference.EqualsTo(0))
+ {
+ slopeValue = xDifference > 0 ? SlopeValue.PositiveZero : SlopeValue.NegativeZero;
+ return null;
+ }
+
+ return yDifference / xDifference;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Types/SQL/AffineTransform.cs b/src/Types/SQL/AffineTransform.cs
new file mode 100644
index 0000000..7dd94d9
--- /dev/null
+++ b/src/Types/SQL/AffineTransform.cs
@@ -0,0 +1,115 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.IO;
+using System.Data.SqlTypes;
+using Microsoft.SqlServer.Server;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Utility;
+using SQLSpatialTools.Sinks.Geometry;
+
+namespace SQLSpatialTools.Types.SQL
+{
+ [Serializable]
+ [SqlUserDefinedType(Format.UserDefined, IsByteOrdered = false, MaxByteSize = -1, IsFixedLength = false)]
+ public sealed class AffineTransform : INullable, IBinarySerialize
+ {
+ private double _ax, _bx, _cx;
+ private double _ay, _by, _cy;
+
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public static AffineTransform Translate(double cx, double cy)
+ {
+ var transform = new AffineTransform {_ax = 1, _by = 1, _cx = cx, _cy = cy};
+ return transform;
+ }
+
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public static AffineTransform Rotate(double angleDeg)
+ {
+ var angle = SpatialUtil.ToRadians(angleDeg);
+ var transform = new AffineTransform {_ax = Math.Cos(angle), _ay = Math.Sin(angle)};
+ transform._bx = -transform._ay;
+ transform._by = transform._ax;
+ return transform;
+ }
+
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public static AffineTransform Scale(double sx, double sy)
+ {
+ var transform = new AffineTransform {_ax = sx, _by = sy};
+ return transform;
+ }
+
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public SqlGeometry Apply(SqlGeometry geometry)
+ {
+ var builder = new SqlGeometryBuilder();
+ geometry.Populate(new GeometryTransformer(builder, this));
+ return builder.ConstructedGeometry;
+ }
+
+ public double GetX(double x, double y)
+ {
+ return _ax * x + _bx * y + _cx;
+ }
+
+ public double GetY(double x, double y)
+ {
+ return _ay * x + _by * y + _cy;
+ }
+
+ public void Read(BinaryReader r)
+ {
+ _ax = r.ReadDouble();
+ _bx = r.ReadDouble();
+ _cx = r.ReadDouble();
+ _ay = r.ReadDouble();
+ _by = r.ReadDouble();
+ _cy = r.ReadDouble();
+ }
+
+ public void Write(BinaryWriter binaryWriter)
+ {
+ binaryWriter.Write(_ax);
+ binaryWriter.Write(_bx);
+ binaryWriter.Write(_cx);
+ binaryWriter.Write(_ay);
+ binaryWriter.Write(_by);
+ binaryWriter.Write(_cy);
+ }
+
+ public bool IsNull => false;
+
+ public static AffineTransform Null
+ {
+ [SqlMethod(IsDeterministic = true, IsPrecise = true)]
+ get => null;
+ }
+
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public static AffineTransform Parse(SqlString str)
+ {
+ var transform = new AffineTransform();
+
+ var args = str.ToString().Split(' ');
+ transform._ax = double.Parse(args[0]);
+ transform._bx = double.Parse(args[1]);
+ transform._cx = double.Parse(args[2]);
+ transform._ay = double.Parse(args[3]);
+ transform._by = double.Parse(args[4]);
+ transform._cy = double.Parse(args[5]);
+
+ return transform;
+ }
+
+ [return: SqlFacet(MaxSize = -1)]
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public override string ToString()
+ {
+ return _ax + " " + _bx + " " + _cx + " " + _ay + " " + _by + " " + _cy;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Types/SQL/SqlProjection.cs b/src/Types/SQL/SqlProjection.cs
new file mode 100644
index 0000000..8058b35
--- /dev/null
+++ b/src/Types/SQL/SqlProjection.cs
@@ -0,0 +1,278 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Data.SqlTypes;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using Microsoft.SqlServer.Server;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Projections;
+using SQLSpatialTools.Sinks.Geography;
+using SQLSpatialTools.Sinks.Geometry;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Types.SQL
+{
+ [Serializable]
+ [SqlUserDefinedType(Format.UserDefined, IsByteOrdered = false, MaxByteSize = -1, IsFixedLength = false)]
+ public sealed class SqlProjection : INullable, IBinarySerialize
+ {
+ private Projection _projection;
+
+ public SqlProjection()
+ {
+ }
+
+ internal SqlProjection(Projection projection)
+ {
+ Debug.Assert(projection != null);
+ _projection = projection;
+ }
+
+ public static SqlProjection Null
+ {
+ [SqlMethod(IsDeterministic = true, IsPrecise = true)]
+ get => new SqlProjection();
+ }
+
+ // TODO use WKT projection description format
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public static SqlProjection Parse(SqlString s)
+ {
+ if (s.Value.Equals("NULL"))
+ return Null;
+
+ var a = s.Value.Split(' ');
+ var cons = Type.GetType(a[0])?.GetConstructor(new[] { typeof(Dictionary) });
+ return cons != null ? new SqlProjection((Projection) cons.Invoke(new object[] {Projection.ParseParameters(a[1])})) : Null;
+ }
+
+ // TODO use WKT projection description format
+ [return: SqlFacet(MaxSize = -1)]
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public override string ToString()
+ {
+ if (_projection == null)
+ {
+ return "NULL";
+ }
+ else
+ {
+ return _projection.GetType().FullName + " " + _projection.Parameters;
+ }
+ }
+
+ public bool IsNull
+ {
+ [SqlMethod(IsDeterministic = true, IsPrecise = true)]
+ get => _projection == null;
+ }
+
+ public void Read(BinaryReader r)
+ {
+ if (r == null) return;
+
+ var name = r.ReadString();
+ if (string.IsNullOrEmpty(name))
+ {
+ _projection = null;
+ }
+ else
+ {
+ var cons = Type.GetType(name)?.GetConstructor(new[] { typeof(Dictionary) });
+ if (cons != null)
+ _projection = (Projection) cons.Invoke(new object[] {Projection.ParseParameters(r.ReadString())});
+ }
+ }
+
+ public void Write(BinaryWriter w)
+ {
+ if (w == null) return;
+ if (_projection == null)
+ {
+ w.Write("");
+ }
+ else
+ {
+ w.Write(_projection.GetType().FullName ?? throw new InvalidOperationException());
+ w.Write(_projection.Parameters);
+ }
+ }
+
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public static SqlProjection AlbersEqualArea(double longitude0, double latitude0, double parallel1, double parallel2)
+ {
+ var parameters = new Dictionary
+ {
+ ["longitude0"] = longitude0,
+ ["latitude0"] = latitude0,
+ ["parallel1"] = parallel1,
+ ["parallel2"] = parallel2
+ };
+ return new SqlProjection(new AlbersEqualAreaProjection(parameters));
+ }
+
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public static SqlProjection Equirectangular(double longitude0, double parallel)
+ {
+ var parameters = new Dictionary {["longitude0"] = longitude0, ["parallel"] = parallel};
+ return new SqlProjection(new EquirectangularProjection(parameters));
+ }
+
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public static SqlProjection LambertConformalConic(double longitude0, double latitude, double fi1, double fi2)
+ {
+ var parameters = new Dictionary
+ {
+ ["longitude0"] = longitude0, ["latitude0"] = latitude, ["fi1"] = fi1, ["fi2"] = fi2
+ };
+ return new SqlProjection(new LambertConformalConicProjection(parameters));
+ }
+
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public static SqlProjection Mercator(double longitude0)
+ {
+ var parameters = new Dictionary {["longitude0"] = longitude0};
+ return new SqlProjection(new MercatorProjection(parameters));
+ }
+
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public static SqlProjection ObliqueMercator(double longitude0, double fi1, double lambda1, double fi2, double lambda2)
+ {
+ var parameters = new Dictionary
+ {
+ ["longitude0"] = longitude0,
+ ["fi1"] = fi1,
+ ["lambda1"] = lambda1,
+ ["fi2"] = fi2,
+ ["lambda2"] = lambda2
+ };
+ return new SqlProjection(new ObliqueMercatorProjection(parameters));
+ }
+
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public static SqlProjection TransverseMercator(double longitude0)
+ {
+ var parameters = new Dictionary {["longitude0"] = longitude0};
+ return new SqlProjection(new TransverseMercatorProjection(parameters));
+ }
+
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public static SqlProjection Gnomonic(double longitude, double latitude)
+ {
+ var parameters = new Dictionary
+ {
+ ["longitude0"] = 0, ["longitude1"] = longitude, ["latitude1"] = latitude
+ };
+ return new SqlProjection(new GnomonicProjection(parameters));
+ }
+
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ private static void ThrowIfArgumentNull(INullable argument, string name)
+ {
+ if (argument == null || argument.IsNull)
+ {
+ throw new ArgumentNullException(name);
+ }
+ }
+
+ // INSTANCE METHODS AND FIELDS
+
+ // Projects geography onto geometry.
+ // Returns valid projected SqlGeometry object.
+ // Constructed geometry will have the same SRID as geography.
+ //
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public SqlGeometry Project(SqlGeography geography)
+ {
+ ThrowIfArgumentNull(geography, "geography");
+ var builder = new SqlGeometryBuilder();
+ geography.Populate(new Projector(this, builder));
+ return builder.ConstructedGeometry;
+ }
+
+ // Unprojects geometry producing geography.
+ // Returns a valid unprojected SqlGeography object.
+ //
+ // SRID taken from the geometry object may not be valid for geography,
+ // in which case a new SRID must be given.
+ //
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public SqlGeography UnprojectWithSRID(SqlGeometry geometry, SqlInt32 newSrid)
+ {
+ ThrowIfArgumentNull(geometry, "geometry");
+ ThrowIfArgumentNull(newSrid, "newSrid");
+ var builder = new SqlGeographyBuilder();
+ geometry.Populate(new UnProjector(this, builder, newSrid.Value));
+ return builder.ConstructedGeography;
+ }
+
+ // Unprojects geometry producing geography.
+ // This method assumes that the SRID of geometry object is valid for geography.
+ // Returns a valid unprojected SqlGeography object.
+ //
+ [SqlMethod(IsDeterministic = true, IsPrecise = false)]
+ public SqlGeography Unproject(SqlGeometry geometry)
+ {
+ ThrowIfArgumentNull(geometry, "geometry");
+ var builder = new SqlGeographyBuilder();
+ geometry.Populate(new UnProjector(this, builder, geometry.STSrid.Value)); // NOTE srid will be reused
+ return builder.ConstructedGeography;
+ }
+
+ // Longitude and latitude are in degrees.
+ // Input Latitude must be in range [-90, 90].
+ // Input Longitude must be in range [-15069, 15069].
+ //
+ public void ProjectPoint(double latitudeDeg, double longitudeDeg, out double x, out double y)
+ {
+ var latitude = MathX.InputLat(latitudeDeg, 90, "latitude");
+ var longitude = MathX.NormalizeLongitudeRad(MathX.InputLong(longitudeDeg, 15069, "longitude") - _projection.CentralLongitudeRad);
+
+ _projection.Project(latitude, longitude, out x, out y);
+
+ if (double.IsNaN(x))
+ {
+ throw new ArgumentOutOfRangeException(nameof(x));
+ }
+ if (double.IsNaN(y))
+ {
+ throw new ArgumentOutOfRangeException(nameof(y));
+ }
+ }
+
+ // Longitude and latitude are in degrees.
+ // Output Latitude will be in range [-90, 90].
+ // Output Longitude will be in range [-180, 180].
+ //
+ public void UnprojectPoint(double x, double y, out double latitudeDeg, out double longitudeDeg)
+ {
+ if (double.IsNaN(x))
+ {
+ throw new ArgumentException(Resource.InputCoordinateIsNaN, nameof(x));
+ }
+ if (double.IsNaN(y))
+ {
+ throw new ArgumentException(Resource.InputCoordinateIsNaN, nameof(y));
+ }
+
+ _projection.Unproject(x, y, out var latitude, out var longitude);
+
+ if (double.IsNaN(latitude) || latitude < -Math.PI / 2 || latitude > Math.PI / 2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(latitude), string.Format(CultureInfo.InvariantCulture, Resource.OutputLatitudeIsOutOfRange, latitude));
+ }
+ if (double.IsNaN(longitude) || longitude < -Math.PI || longitude > Math.PI)
+ {
+ throw new ArgumentOutOfRangeException(nameof(longitude), string.Format(CultureInfo.InvariantCulture, Resource.OutputLongitudeIsOutOfRange, longitude));
+ }
+
+ latitudeDeg = MathX.Clamp(90, SpatialUtil.ToDegrees(latitude));
+ longitudeDeg = MathX.NormalizeLongitudeDeg(SpatialUtil.ToDegrees(longitude + _projection.CentralLongitudeRad));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Utility/Vector3.cs b/src/Types/Vector3.cs
similarity index 68%
rename from Utility/Vector3.cs
rename to src/Types/Vector3.cs
index 699c9e7..6ec3020 100644
--- a/Utility/Vector3.cs
+++ b/src/Types/Vector3.cs
@@ -1,98 +1,101 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-
-using System;
-
-namespace SQLSpatialTools
-{
- internal class Vector3
- {
- // Fields.
- public readonly double x, y, z;
-
- // Constructors.
- public Vector3(double x, double y, double z)
- {
- this.x = x;
- this.y = y;
- this.z = z;
- }
-
- // Addition.
- public static Vector3 operator +(Vector3 a, Vector3 b)
- {
- return new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
- }
-
- // Subtraction.
- public static Vector3 operator -(Vector3 a, Vector3 b)
- {
- return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
- }
-
- // Multiplication.
- public static Vector3 operator *(Vector3 vector, double a)
- {
- return new Vector3(vector.x * a, vector.y * a, vector.z * a);
- }
-
- // Division.
- public static Vector3 operator /(Vector3 v, double a)
- {
- return v * (1 / a);
- }
-
- // Unit vector with the same direction.
- public Vector3 Unitize()
- {
- return this / VectorLength();
- }
-
- // Dot product.
- public static double operator *(Vector3 a, Vector3 b)
- {
- return b.x * a.x + b.y * a.y + b.z * a.z;
- }
-
- // The square if the length.
- public double LengthSquared()
- {
- return this * this;
- }
-
- // The length of the vector.
- public double VectorLength()
- {
- return Math.Sqrt(LengthSquared());
- }
-
- // Squared distance between vectors a and b.
- public double DistanceSquared(Vector3 a)
- {
- return (this - a) * (this - a);
- }
-
- // Distance between vectors a and b.
- public double Distance(Vector3 a)
- {
- return Math.Sqrt(DistanceSquared(a));
- }
-
- // Cross product between vectors a and b.
- public Vector3 CrossProduct(Vector3 a)
- {
- return new Vector3(y * a.z - z * a.y, z * a.x - x * a.z, x * a.y - y * a.x);
- }
-
- // Angle in radians between vectors a and b.
- public double Angle(Vector3 a)
- {
- return 2 * Math.Asin(this.Distance(a) / (2 * a.VectorLength()));
- }
-
- // Angle in degrees between vectors a and b.
- public double AngleInDegrees(Vector3 a)
- {
- return Util.ToDegrees(this.Angle(a));
- }
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.Types
+{
+ internal class Vector3
+ {
+ // Fields.
+ public readonly double X, Y, Z;
+
+ // Constructors.
+ public Vector3(double x, double y, double z)
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ }
+
+ // Addition.
+ public static Vector3 operator +(Vector3 a, Vector3 b)
+ {
+ return new Vector3(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
+ }
+
+ // Subtraction.
+ public static Vector3 operator -(Vector3 a, Vector3 b)
+ {
+ return new Vector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
+ }
+
+ // Multiplication.
+ public static Vector3 operator *(Vector3 vector, double a)
+ {
+ return new Vector3(vector.X * a, vector.Y * a, vector.Z * a);
+ }
+
+ // Division.
+ public static Vector3 operator /(Vector3 v, double a)
+ {
+ return v * (1 / a);
+ }
+
+ // Unit vector with the same direction.
+ public Vector3 Unitize()
+ {
+ return this / VectorLength();
+ }
+
+ // Dot product.
+ public static double operator *(Vector3 a, Vector3 b)
+ {
+ return b.X * a.X + b.Y * a.Y + b.Z * a.Z;
+ }
+
+ // The square if the length.
+ public double LengthSquared()
+ {
+ return this * this;
+ }
+
+ // The length of the vector.
+ public double VectorLength()
+ {
+ return Math.Sqrt(LengthSquared());
+ }
+
+ // Squared distance between vectors a and b.
+ public double DistanceSquared(Vector3 a)
+ {
+ return (this - a) * (this - a);
+ }
+
+ // Distance between vectors a and b.
+ public double Distance(Vector3 a)
+ {
+ return Math.Sqrt(DistanceSquared(a));
+ }
+
+ // Cross product between vectors a and b.
+ public Vector3 CrossProduct(Vector3 a)
+ {
+ return new Vector3(Y * a.Z - Z * a.Y, Z * a.X - X * a.Z, X * a.Y - Y * a.X);
+ }
+
+ // Angle in radians between vectors a and b.
+ public double Angle(Vector3 a)
+ {
+ return 2 * Math.Asin(Distance(a) / (2 * a.VectorLength()));
+ }
+
+ // Angle in degrees between vectors a and b.
+ public double AngleInDegrees(Vector3 a)
+ {
+ return SpatialUtil.ToDegrees(Angle(a));
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Types/Vertex.cs b/src/Types/Vertex.cs
new file mode 100644
index 0000000..a2ee836
--- /dev/null
+++ b/src/Types/Vertex.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.SqlServer.Types;
+
+namespace SQLSpatialTools.Types
+{
+ internal struct Vertex
+ {
+ private readonly double _x;
+ private readonly double _y;
+ private readonly double? _z;
+ private readonly double? _m;
+
+ public Vertex(double x, double y, double? z, double? m)
+ {
+ _x = x;
+ _y = y;
+ _z = z;
+ _m = m;
+ }
+
+ public void BeginFigure(IGeometrySink110 sink) { sink.BeginFigure(_x, _y, _z, _m); }
+ public void AddLine(IGeometrySink110 sink) { sink.AddLine(_x, _y, _z, _m); }
+
+ public void BeginFigure(IGeographySink110 sink) { sink.BeginFigure(_x, _y, _z, _m); }
+ public void AddLine(IGeographySink110 sink) { sink.AddLine(_x, _y, _z, _m); }
+ }
+}
\ No newline at end of file
diff --git a/src/Utility/EnumsAndConstants.cs b/src/Utility/EnumsAndConstants.cs
new file mode 100644
index 0000000..df2ba07
--- /dev/null
+++ b/src/Utility/EnumsAndConstants.cs
@@ -0,0 +1,155 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+
+namespace SQLSpatialTools.Utility
+{
+ ///
+ /// Enum for Open Geo spatial Consortium Type Names
+ ///
+ public enum OGCType
+ {
+ [StringValue("Point")]
+ Point,
+
+ [StringValue("LineString")]
+ LineString,
+
+ [StringValue("CircularString")]
+ CircularString,
+
+ [StringValue("CompoundCurve")]
+ CompoundCurve,
+
+ [StringValue("Polygon")]
+ Polygon,
+
+ [StringValue("CurvePolygon")]
+ CurvePolygon,
+
+ [StringValue("GeometryCollection")]
+ GeometryCollection,
+
+ [StringValue("MultiPoint")]
+ MultiPoint,
+
+ [StringValue("MultiLineString")]
+ MultiLineString,
+
+ [StringValue("MultiPolygon")]
+ MultiPolygon
+ }
+
+ public enum DimensionalInfo
+ {
+ None,
+ [StringValue("2 Dimensional point, with x and y")]
+ Dim2D,
+ [StringValue("2 Dimensional point, with x, y and measure")]
+ Dim2DWithMeasure,
+ [StringValue("3 Dimensional point, with x, y and z")]
+ Dim3D,
+ [StringValue("3 Dimensional point, with x, y, z with measure")]
+ Dim3DWithMeasure
+ }
+
+ public static class Constants
+ {
+ public const int DefaultSRID = 4326;
+
+ // 1 cm tolerance in most SRIDs
+ public const double Tolerance = 0.5;
+
+ // Point sql char format.
+ public const string PointSqlCharFormat = "POINT({0} {1} {2} {3})";
+ }
+
+ public enum SlopeValue
+ {
+ None,
+ PositiveZero,
+ NegativeZero,
+ PositiveInfinity,
+ NegativeInfinity
+ }
+
+ ///
+ /// This attribute is used to represent a string value
+ /// for a value in an enum.
+ ///
+ public sealed class StringValueAttribute : Attribute
+ {
+ ///
+ /// Holds the string value for a value in an enum.
+ ///
+ public string StringValue { get; }
+
+ ///
+ /// Constructor used to initialize a StringValue Attribute
+ ///
+ ///
+ public StringValueAttribute(string value)
+ {
+ StringValue = value;
+ }
+ }
+
+ public enum LRSErrorCodes
+ {
+ [StringValue("Valid LRS segment")]
+ ValidLRS = 1,
+
+ [StringValue("Invalid LRS segment")]
+ InvalidLRS = 13331,
+
+ [StringValue("Invalid LRS point")]
+ InvalidLRSPoint = 13332,
+
+ [StringValue("Invalid LRS measure")]
+ InvalidLRSMeasure = 13333,
+
+ [StringValue("LRS segments not connected")]
+ LRSSegmentNotConnected = 13334,
+
+ [StringValue("LRS segment is not defined")]
+ LRSSegmentNotDefined = 13335
+ }
+
+ ///
+ /// Progression of Geom Segment based on measure.
+ ///
+ public enum LinearMeasureProgress : short
+ {
+ None = 0,
+ Increasing = 1,
+ Decreasing = 2
+ }
+
+ ///
+ /// Merging position of Geom Segments
+ ///
+ public enum MergePosition
+ {
+ None = 0,
+ StartStart = 1,
+ StartEnd = 2,
+ EndStart = 3,
+ EndEnd = 4,
+ BothEnds = 5,
+ CrossEnds = 6
+ }
+
+ ///
+ /// Merge Input Segments Geometry type.
+ ///
+ public enum MergeInputType
+ {
+ None = 0,
+ LSLS = 1,
+ LSMLS = 2,
+ MLSLS = 3,
+ MLSMLS = 4
+ }
+}
diff --git a/src/Utility/ErrorMessage.cs b/src/Utility/ErrorMessage.cs
new file mode 100644
index 0000000..3e633fd
--- /dev/null
+++ b/src/Utility/ErrorMessage.cs
@@ -0,0 +1,27 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+namespace SQLSpatialTools.Utility
+{
+ public static class ErrorMessage
+ {
+ public const string LineStringCompatible = "LINESTRING is currently the only spatial type supported.";
+ public const string LRSCompatible = "POINT, LINESTRING or MULTILINE STRING is currently the only spatial type supported.";
+ public const string LineOrMultiLineStringCompatible = "LINESTRING or MULTILINE STRING is currently the only spatial type supported.";
+ public const string LineOrPointCompatible = "LINESTRING or POINT is currently the only spatial type supported.";
+ public const string MultiLineStringCompatible = "MULTILINE STRING is currently the only spatial type supported.";
+ public const string PointCompatible = "Start and End geometry must be a point.";
+ public const string SRIDCompatible = @"SRID's of geography\geometry objects doesn't match.";
+ public const string MeasureRange = "Measure not within range.";
+ public const string LinearMeasureRange = "The given measure for linear referencing was out of range.";
+ public const string WKT3DOnly = "Input WKT should only have three dimensions!";
+ public const string LinearGeometryMeasureMustBeInRange = "{0} is not within the measure range {1} : {2} of the linear geometry.";
+ public const string DistanceMustBeBetweenTwoPoints = "The distance value provided exceeds the distance between the two points.";
+ public const string DistanceMustBePositive = "The distance must be positive.";
+ public const string TwoDimensionalCoordinates = "Cannot operate on 2 Dimensional co-ordinates without measure values.";
+ public const string InvalidElementIndex = "Invalid index for element to be extracted.";
+ public const string InvalidSubElementIndex = "Invalid index for sub-element to be extracted.";
+ public const string InvalidGeometry = "Invalid geometry";
+ }
+}
diff --git a/src/Utility/SQLTypeConversions.cs b/src/Utility/SQLTypeConversions.cs
new file mode 100644
index 0000000..52123c7
--- /dev/null
+++ b/src/Utility/SQLTypeConversions.cs
@@ -0,0 +1,31 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System.Data.SqlTypes;
+
+namespace SQLSpatialTools.Utility
+{
+ internal static class SQLTypeConversions
+ {
+ public class Numeric
+ {
+ private readonly int _value;
+
+ private Numeric(int value)
+ {
+ _value = value;
+ }
+
+ public static implicit operator int(Numeric v)
+ {
+ return v._value;
+ }
+
+ public static implicit operator Numeric(SqlInt32 value)
+ {
+ return new Numeric((int)value);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Utility/SpatialExtensions.cs b/src/Utility/SpatialExtensions.cs
new file mode 100644
index 0000000..d085f47
--- /dev/null
+++ b/src/Utility/SpatialExtensions.cs
@@ -0,0 +1,1332 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Data.SqlTypes;
+using System.Linq;
+using Microsoft.SqlServer.Types;
+using System.Globalization;
+using SQLSpatialTools.Sinks.Geometry;
+using SQLSpatialTools.Types;
+using System.Text.RegularExpressions;
+using System.Text;
+using System.Collections.Generic;
+
+namespace SQLSpatialTools.Utility
+{
+ public static class SpatialExtensions
+ {
+ private const string _geomTypeRegexString = @"^(?\w+)?\s?\(";
+ private const string _polygonSegmentRegex = @"POLYGON\s*?\((?.*)\)";
+ private const string _multipolygonSegmentRegex = @"MULTIPOLYGON\s*?\((?.*)\)";
+ private const string _segmentMatchRegex = @"\(.*?\)";
+
+ #region OGC Type Checks
+
+ ///
+ /// Check if Geometry is of type point,
+ /// it returns true, if the geometry is of type point
+ /// Instead of checking the STGeometryType directly, this utility method parses the points of the geometry, which make sure it returns true, even if the geometry contains invalid co-ordinates
+ ///
+ ///
+ ///
+ public static bool CheckGeomPoint(this SqlGeometry sqlGeometry)
+ {
+ try
+ {
+ return GetPoint(sqlGeometry).STGeometryType().Compare(OGCType.Point.GetString());
+ }
+ catch (SqlNullValueException)
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Check if Geometry is Point
+ ///
+ ///
+ ///
+ public static bool IsPoint(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.STGeometryType().Compare(OGCType.Point.GetString());
+ }
+
+ ///
+ /// Check if Geometry is Point; Works on invalid types too.
+ ///
+ /// Geometry in WKT format
+ ///
+ public static bool IsPoint(this string wktGeometry)
+ {
+ return Regex.IsMatch(wktGeometry, @"^POINT\s*\(.*\)", RegexOptions.IgnoreCase);
+ }
+
+ ///
+ /// Check if Geography type is Point
+ ///
+ /// SQL Geography
+ ///
+ public static bool IsPoint(this SqlGeography sqlGeography)
+ {
+ return sqlGeography.STGeometryType().Compare(OGCType.Point.GetString());
+ }
+
+ ///
+ /// Check if Geometry is LineString
+ ///
+ ///
+ /// true; false
+ public static bool IsLineString(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.STGeometryType().Compare(OGCType.LineString.GetString());
+ }
+
+ ///
+ /// Check if Geometry is CircularString
+ ///
+ ///
+ /// true; false
+ public static bool IsCircularString(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.STGeometryType().Compare(OGCType.CircularString.GetString());
+ }
+
+ ///
+ /// Check if Geometry is CompoundCurve
+ ///
+ ///
+ /// true; false
+ public static bool IsCompoundCurve(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.STGeometryType().Compare(OGCType.CompoundCurve.GetString());
+ }
+
+ ///
+ /// Check if Geometry is Polygon
+ ///
+ ///
+ /// true; false
+ public static bool IsPolygon(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.STGeometryType().Compare(OGCType.Polygon.GetString());
+ }
+
+ ///
+ /// Check if Geometry is Polygon
+ ///
+ ///
+ /// true; false
+ public static bool IsPolygon(this SqlGeometry sqlGeometry, bool checkValid = true)
+ {
+ if (checkValid)
+ return sqlGeometry.IsPolygon();
+ return GetGeomText(sqlGeometry.ToString()).Equals("POLYGON");
+ }
+
+ ///
+ /// Check if Geometry is CurvePolygon
+ ///
+ ///
+ /// true; false
+ public static bool IsCurvePolygon(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.STGeometryType().Compare(OGCType.CurvePolygon.GetString());
+ }
+
+ ///
+ /// Check if Geometry is CurvePolygon
+ ///
+ ///
+ /// true; false
+ public static bool IsCurvePolygon(this SqlGeometry sqlGeometry, bool checkValid = true)
+ {
+ if (checkValid)
+ return sqlGeometry.IsCurvePolygon();
+ return GetGeomText(sqlGeometry.ToString()).Equals("CURVEPOLYGON");
+ }
+
+ ///
+ /// Check if Geometry is GeometryCollection
+ ///
+ ///
+ /// true; false
+ public static bool IsGeometryCollection(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.STGeometryType().Compare(OGCType.GeometryCollection.GetString());
+ }
+
+ ///
+ /// Check if Geometry is MultiPoint
+ ///
+ ///
+ /// true; false
+ public static bool IsMultiPoint(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.STGeometryType().Compare(OGCType.MultiPoint.GetString());
+ }
+
+ ///
+ /// Check if Geometry is MultiLineString
+ ///
+ ///
+ /// true; false
+ public static bool IsMultiLineString(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.STGeometryType().Compare(OGCType.MultiLineString.GetString());
+ }
+
+ ///
+ /// Check if Geometry is MultiPolygon
+ ///
+ ///
+ /// true; false
+ public static bool IsMultiPolygon(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.STGeometryType().Compare(OGCType.MultiPolygon.GetString());
+ }
+
+ ///
+ /// Check if Geometry is MultiPolygon
+ ///
+ ///
+ /// true; false
+ public static bool IsMultiPolygon(this SqlGeometry sqlGeometry, bool checkValid = true)
+ {
+ if (checkValid)
+ return sqlGeometry.IsMultiPolygon();
+ return GetGeomText(sqlGeometry.ToString()).Equals("MULTIPOLYGON");
+ }
+
+ ///
+ /// Get geometry type.
+ ///
+ /// Geometry text
+ /// Geometry type
+ public static string GetGeomText(this string geomText)
+ {
+ var match = Regex.Match(geomText, _geomTypeRegexString);
+ return match.Groups["type"].Value.ToUpper();
+ }
+
+ #endregion
+
+ #region Measures Tolerance
+
+ ///
+ /// Get start point measure of Geometry
+ ///
+ /// Input Geometry
+ ///
+ public static double GetStartPointMeasure(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.STStartPoint().M.IsNull
+ ? 0
+ : sqlGeometry.STStartPoint().M.Value;
+ }
+
+ ///
+ /// Get end point measure of Geometry
+ ///
+ /// Input Geometry
+ ///
+ public static double GetEndPointMeasure(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.STEndPoint().M.IsNull
+ ? sqlGeometry.STLength().Value
+ : sqlGeometry.STEndPoint().M.Value;
+ }
+
+ ///
+ /// Determines whether the geometry segment is not null or empty.
+ ///
+ /// The SQL geometry.
+ ///
+ /// true if [is not null or empty] [the specified SQL geometry]; otherwise, false.
+ ///
+ public static bool IsNotNullOrEmpty(this SqlGeometry sqlGeometry)
+ {
+ return !sqlGeometry.IsNullOrEmpty();
+ }
+
+ ///
+ /// Determines whether the geometry segment is null or empty.
+ ///
+ /// The SQL geometry.
+ ///
+ /// true if [is null or empty] [the specified SQL geometry]; otherwise, false.
+ ///
+ public static bool IsNullOrEmpty(this SqlGeometry sqlGeometry)
+ {
+ if (sqlGeometry == null)
+ return true;
+ return (bool)(sqlGeometry.IsNull || sqlGeometry.STIsEmpty());
+ }
+
+ ///
+ /// for checking whether geometry measure is increasing or decreasing
+ ///
+ ///
+ ///
+ public static bool STHasLinearMeasure(this SqlGeometry geometry)
+ {
+ if (geometry.IsNullOrEmpty() || !geometry.STIsValid())
+ return false;
+
+ var numPoints = geometry.STNumPoints();
+ var measureProgress = geometry.STLinearMeasureProgress();
+
+ var previousM = 0.0;
+ for (var iterator = 1; iterator <= numPoints; iterator++)
+ {
+ var currentPoint = geometry.STPointN(iterator);
+ if (!currentPoint.HasM)
+ return false;
+
+ if (iterator == 1)
+ {
+ previousM = currentPoint.M.Value;
+ continue;
+ }
+
+ switch (measureProgress)
+ {
+ case LinearMeasureProgress.Increasing when previousM > currentPoint.M:
+ case LinearMeasureProgress.Decreasing when previousM < currentPoint.M:
+ return false;
+ default:
+ previousM = currentPoint.M.Value;
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// To check whether start measure value is equal to End Measure value
+ ///
+ ///
+ /// equal measure value
+ public static bool STHasEqualStartAndEndMeasure(this SqlGeometry geometry)
+ {
+ return Math.Abs(geometry.GetStartPointMeasure() - geometry.GetEndPointMeasure()) < 0.0000001;
+ }
+
+ ///
+ /// Get the linear progression of the geometry based on measure value; whether increasing or decreasing.
+ ///
+ /// The input SqlGeometry.
+ /// Increasing or Decreasing
+ public static LinearMeasureProgress STLinearMeasureProgress(this SqlGeometry geometry)
+ {
+ if (geometry.IsNullOrEmpty())
+ return LinearMeasureProgress.None;
+
+ if (geometry.IsPoint())
+ return LinearMeasureProgress.Increasing;
+
+ var startPoint = geometry.STStartPoint();
+ var endPoint = geometry.STEndPoint();
+ if (!geometry.STStartPoint().HasM || !geometry.STEndPoint().HasM)
+ return LinearMeasureProgress.None;
+
+ return (endPoint.M - startPoint.M > 0 ? LinearMeasureProgress.Increasing : LinearMeasureProgress.Decreasing);
+ }
+
+ ///
+ /// Gets the distance between two x,y co-ordinates.
+ ///
+ /// The x1.
+ /// The y1.
+ /// The x2.
+ /// The y2.
+ ///
+ public static double GetDistance(double x1, double y1, double x2, double y2)
+ {
+ var xCoordinateDiff = Math.Pow(Math.Abs(x2 - x1), 2);
+ var yCoordinateDiff = Math.Pow(Math.Abs(y2 - y1), 2);
+ return Math.Sqrt(xCoordinateDiff + yCoordinateDiff);
+ }
+
+ ///
+ /// Determines whether the specified distance is within tolerance.
+ ///
+ /// The distance.
+ /// The tolerance.
+ ///
+ /// true if the specified distance is tolerable; otherwise, false.
+ ///
+ public static bool IsTolerable(this double distance, double tolerance)
+ {
+ var digitToCompare = distance;
+ var decDigits = tolerance.GetPrecisionLength();
+
+ if (decDigits > 0)
+ digitToCompare = Math.Round(distance, decDigits);
+
+ return digitToCompare <= tolerance;
+ }
+
+ ///
+ /// Gets the precision length
+ ///
+ /// The decimal digit.
+ ///
+ public static int GetPrecisionLength(this double decimalDigit)
+ {
+ var decimalStr = decimalDigit.ToString(CultureInfo.CurrentCulture);
+ var decimalIndex = decimalStr.IndexOf(".", StringComparison.CurrentCulture);
+
+ return decimalIndex > 0 ? decimalStr.Substring(decimalIndex).Length : 0;
+ }
+
+ ///
+ /// Determines whether the x y distance between start and point is tolerable.
+ ///
+ /// The start point.
+ /// The end point.
+ /// The tolerance.
+ ///
+ /// true if the specified start and end point distance is tolerable; otherwise, false.
+ ///
+ public static bool IsWithinTolerance(this SqlGeometry startPoint, SqlGeometry endPoint, double tolerance)
+ {
+ return IsXYWithinRange(startPoint, endPoint, tolerance);
+ }
+
+ ///
+ /// Determines whether the x y distance between 2 x,y points is within tolerance
+ ///
+ /// The x1.
+ /// The y1.
+ /// The x2.
+ /// The y2.
+ /// The tolerance.
+ ///
+ /// true if distance between 2 points are within tolerance; otherwise, false.
+ ///
+ public static bool IsWithinTolerance(double x1, double y1, double x2, double y2, double tolerance)
+ {
+ return IsXYWithinRange(x1, y1, x2, y2, tolerance);
+ }
+
+ #endregion
+
+ #region Range Validations
+
+ ///
+ /// Check whether the measure falls withing the start and end measure.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool IsWithinRange(this double? currentMeasure, double? startMeasure, double? endMeasure)
+ {
+ if (currentMeasure.HasValue && startMeasure.HasValue && endMeasure.HasValue)
+ return IsWithinRange(currentMeasure.Value, startMeasure.Value, endMeasure.Value);
+ return false;
+ }
+
+ ///
+ /// Check whether the measure falls withing the start and end measure.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool IsWithinRange(this double currentMeasure, double startMeasure, double endMeasure)
+ {
+ return (
+ // if line segment measure is increasing start ----> end
+ (currentMeasure >= startMeasure && currentMeasure <= endMeasure)
+ ||
+ // if line segment measure is increasing start <---- end
+ (currentMeasure >= endMeasure && currentMeasure <= startMeasure)
+ );
+ }
+
+ ///
+ /// Check whether the measure falls withing the start and end measure of geometry.
+ ///
+ ///
+ ///
+ ///
+ public static bool IsWithinRange(this double currentMeasure, SqlGeometry sqlGeometry)
+ {
+ var startMeasure = sqlGeometry.GetStartPointMeasure();
+ var endMeasure = sqlGeometry.GetEndPointMeasure();
+ return IsWithinRange(currentMeasure, startMeasure, endMeasure);
+ }
+
+ ///
+ /// Check whether the measure falls between start and end geometry points
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool IsWithinRange(this double currentMeasure, SqlGeometry startPoint, SqlGeometry endPoint)
+ {
+ var startMeasure = startPoint.GetStartPointMeasure();
+ var endMeasure = endPoint.GetEndPointMeasure();
+ return IsWithinRange(currentMeasure, startMeasure, endMeasure);
+ }
+
+ ///
+ /// Checks whether difference of X and Y point between source and point to compare is within tolerable range
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool IsXYWithinRange(this SqlGeometry sourcePoint, SqlGeometry pointToCompare, double tolerance = 0.0F)
+ {
+ return Math.Abs(sourcePoint.STX.Value - pointToCompare.STX.Value) <= tolerance &&
+ Math.Abs(sourcePoint.STY.Value - pointToCompare.STY.Value) <= tolerance;
+ }
+
+ ///
+ /// Determines whether [is x y within range] [the specified x1].
+ ///
+ /// The x1.
+ /// The y1.
+ /// The x2.
+ /// The y2.
+ /// The tolerance.
+ ///
+ /// true if [is x y within range] [the specified x1]; otherwise, false.
+ ///
+ public static bool IsXYWithinRange(double x1, double y1, double x2, double y2, double tolerance = 0.0F)
+ {
+ return Math.Abs(x1 - x2) <= tolerance &&
+ Math.Abs(y1 - y2) <= tolerance;
+ }
+
+ ///
+ /// Determines whether the input start or end measure matches the start or end point measure of geometry.
+ ///
+ /// The geom start measure.
+ /// The geom end measure.
+ /// The input start measure.
+ /// The input end measure.
+ ///
+ /// true if input measure matches end or start point measure of geometry; otherwise, false.
+ ///
+ public static bool IsExtremeMeasuresMatch(double geomStartMeasure, double geomEndMeasure, double inputStartMeasure, double inputEndMeasure)
+ {
+ var inputStartCheck1 = inputStartMeasure.Equals(geomStartMeasure);
+ var inputStartCheck2 = inputStartMeasure.Equals(geomEndMeasure);
+
+ var inputEndCheck1 = inputEndMeasure.Equals(geomStartMeasure);
+ var inputEndCheck2 = inputEndMeasure.Equals(geomEndMeasure);
+
+ return (inputStartCheck1 || inputStartCheck2) || (inputEndCheck1 || inputEndCheck2);
+ }
+
+ ///
+ ///
+ /// Get offset measure between the two geometry.
+ ///
+ /// Subtracts end measure of first geometry against the first measure of second geometry.
+ ///
+ ///
+ /// First Geometry
+ /// Second Geometry
+ /// Difference in measure
+ public static double? GetOffset(this SqlGeometry sqlGeometry1, SqlGeometry sqlGeometry2)
+ {
+ return sqlGeometry1.GetEndPointMeasure() - sqlGeometry2.GetStartPointMeasure();
+ }
+
+ ///
+ /// Get offset measure between the two point geometry.
+ /// Subtracts end measure of first point and second point.
+ ///
+ /// First Point
+ /// Second Point
+ /// Difference in measure
+ public static double GetPointOffset(this SqlGeometry point1, SqlGeometry point2)
+ {
+ return point1.M.Value - point2.M.Value;
+ }
+
+ #endregion
+
+ #region Sql Types Comparison
+
+ ///
+ /// Compares sql string for equality.
+ ///
+ /// SQL string
+ /// Target OGC type string to compare
+ ///
+ public static bool Compare(this SqlString sqlString, string targetString)
+ {
+ if (string.IsNullOrEmpty(targetString))
+ return false;
+
+ var convertString = sqlString.ToString();
+
+ return !string.IsNullOrEmpty(convertString) && convertString.ToLowerInvariant().Equals(targetString.ToLowerInvariant(), StringComparison.CurrentCulture);
+ }
+
+ #endregion
+
+ #region Enum Attribute Extension
+
+ ///
+ /// Will get the string value for a given enums value, this will
+ /// only work if you assign the StringValue attribute to
+ /// the items in your enum.
+ ///
+ ///
+ ///
+ public static string GetString(this OGCType value)
+ {
+ return GetStringAttributeValue(value);
+ }
+
+ ///
+ /// Will get the string value for a given enums value, this will
+ /// only work if you assign the StringValue attribute to
+ /// the items in your enum.
+ ///
+ ///
+ ///
+ public static string GetString(this DimensionalInfo value)
+ {
+ return GetStringAttributeValue(value);
+ }
+
+ ///
+ /// Will get the string value for a given enums value, this will
+ /// only work if you assign the StringValue attribute to
+ /// the items in your enum.
+ ///
+ ///
+ ///
+ public static string GetString(this LRSErrorCodes value)
+ {
+ return GetStringAttributeValue(value);
+ }
+
+ ///
+ /// Gets the string attribute value.
+ ///
+ ///
+ /// The value.
+ ///
+ private static string GetStringAttributeValue(this T value)
+ {
+ // Get the type
+ var type = value.GetType();
+
+ // Get field info for this type
+ var fieldInfo = type.GetField(value.ToString());
+
+ // Get the string value attributes
+ // Return the first if there was a match.
+ return fieldInfo.GetCustomAttributes(typeof(StringValueAttribute), false)
+ is StringValueAttribute[] attributes && attributes.Length > 0 ? attributes[0].StringValue : null;
+ }
+
+ ///
+ /// Get the int value of enum
+ ///
+ /// The linear measure progress.
+ /// Returns the implicit integer value of respective enum
+ public static short Value(this LinearMeasureProgress linearMeasureProgress)
+ {
+ return (short)linearMeasureProgress;
+ }
+
+ ///
+ /// Get the string value for the specified LRS Error Code.
+ ///
+ /// The LRS error codes.
+ /// String Value of LRS Error Code.
+ public static string Value(this LRSErrorCodes lrsErrorCodes)
+ {
+ return lrsErrorCodes == LRSErrorCodes.ValidLRS ? "TRUE" : ((short)lrsErrorCodes).ToString(CultureInfo.CurrentCulture);
+ }
+
+ #endregion
+
+ #region Exception Handling
+
+ ///
+ /// Throws Argument Exception if the element index to be extracted is greater than the input geometry.
+ ///
+ internal static void ThrowInvalidElementIndex()
+ {
+ throw new ArgumentException(ErrorMessage.InvalidElementIndex);
+ }
+
+ ///
+ /// Throws Argument Exception if the sub-element index to be extracted is greater than the input geometry.
+ ///
+ internal static void ThrowInvalidSubElementIndex()
+ {
+ throw new ArgumentException(ErrorMessage.InvalidSubElementIndex);
+ }
+
+ ///
+ /// Throws Argument Exception if the geometry is not valid
+ ///
+ internal static void ThrowIfInvalidGeometry()
+ {
+ throw new ArgumentException(ErrorMessage.InvalidGeometry);
+ }
+ ///
+ /// Throw if input geometry is not a LRS Geometry collection POINT, LINESTRING or MULTILINESTRING.
+ ///
+ /// Input Sql Geometry
+ internal static void ThrowIfNotLRSType(params SqlGeometry[] sqlGeometries)
+ {
+ if (sqlGeometries.Any(geom => !geom.IsLRSType()))
+ throw new ArgumentException(ErrorMessage.LRSCompatible);
+ }
+
+ ///
+ /// Throw if input geometry is not a Point.
+ ///
+ /// Input Sql Geometry
+ /// Sql Geometries
+ internal static void ThrowIfNotPoint(SqlGeometry sqlGeometry, params SqlGeometry[] sqlGeometries)
+ {
+ var geometries = (new[] { sqlGeometry }).Concat(sqlGeometries);
+ if (geometries.Any(geom => !geom.IsOfSupportedTypes(OpenGisGeometryType.Point)))
+ throw new ArgumentException(ErrorMessage.PointCompatible);
+ }
+
+ ///
+ /// Throw if input geometry is not a line string.
+ ///
+ /// Input Sql Geometry
+ /// Sql Geometries
+ internal static void ThrowIfNotLine(SqlGeometry sqlGeometry, params SqlGeometry[] sqlGeometries)
+ {
+ var geometries = (new[] { sqlGeometry }).Concat(sqlGeometries);
+ if (geometries.Any(geom => !geom.IsOfSupportedTypes(OpenGisGeometryType.LineString)))
+ throw new ArgumentException(ErrorMessage.LineStringCompatible);
+ }
+
+ ///
+ /// Throw if input geometry is not a line string or multiline string.
+ ///
+ /// Input Sql Geometry
+ /// Sql Geometries
+ internal static void ThrowIfNotLineOrMultiLine(SqlGeometry sqlGeometry, params SqlGeometry[] sqlGeometries)
+ {
+ var geometries = (new[] { sqlGeometry }).Concat(sqlGeometries);
+ if (geometries.Any(geom => !geom.IsOfSupportedTypes(OpenGisGeometryType.LineString, OpenGisGeometryType.MultiLineString)))
+ throw new ArgumentException(ErrorMessage.LineOrMultiLineStringCompatible);
+ }
+
+ ///
+ /// Throw if SRIDs of two geometries doesn't match.
+ ///
+ /// Source Sql Geometry
+ /// Target Sql Geometry
+ internal static void ThrowIfSRIDDoesNotMatch(SqlGeometry sourceGeometry, SqlGeometry targetGeometry)
+ {
+ if (!sourceGeometry.STSrid.Equals(targetGeometry.STSrid))
+ throw new ArgumentException(ErrorMessage.SRIDCompatible);
+ }
+
+ ///
+ /// Throw if measure is not withing the range of two geometries.
+ ///
+ /// Measure
+ /// Start Point Sql Geometry
+ /// End Point Sql Geometry
+ internal static void ThrowIfMeasureIsNotInRange(double measure, SqlGeometry startPoint, SqlGeometry endPoint)
+ {
+ if (!measure.IsWithinRange(startPoint, endPoint))
+ throw new ArgumentException(ErrorMessage.MeasureRange);
+ }
+
+ ///
+ /// Throw if measure is not linear for LRS System.
+ ///
+ /// SQL Geometry
+ internal static void ThrowIfMeasureNotLinear(SqlGeometry inputGeometry)
+ {
+ if (!inputGeometry.STHasLinearMeasure())
+ throw new ArgumentException(ErrorMessage.LinearMeasureRange);
+ }
+
+ ///
+ /// Throw if measure is not withing the range of two geometries.
+ ///
+ /// Measure
+ /// Input Sql Geometry
+ internal static void ThrowIfMeasureIsNotInRange(double measure, SqlGeometry sqlGeometry)
+ {
+ if (!measure.IsWithinRange(sqlGeometry))
+ ThrowLRSError(LRSErrorCodes.InvalidLRSMeasure);
+ }
+
+ ///
+ /// Check if the geometry collection is of POINT, LINESTRING, MULTILINESTRING.
+ ///
+ ///
+ ///
+ public static bool IsLRSType(this SqlGeometry geometry)
+ {
+ return geometry.IsOfSupportedTypes(OpenGisGeometryType.Point, OpenGisGeometryType.LineString, OpenGisGeometryType.MultiLineString);
+ }
+
+ ///
+ /// Check if the input geometry is of the supported specified type
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool IsOfSupportedTypes(this SqlGeometry geometry, OpenGisGeometryType supportedType, params OpenGisGeometryType[] additionalSupportedTypes)
+ {
+ var geomSink = new GISTypeCheckGeometrySink((new[] { supportedType }).Concat(additionalSupportedTypes).ToArray());
+ geometry.Populate(geomSink);
+ return geomSink.IsCompatible();
+ }
+
+ ///
+ /// Throws ArgumentException based on message format and parameters
+ ///
+ /// Message format
+ /// Arguments to be appended with format
+ public static void ThrowException(string messageFormat, params object[] args)
+ {
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, messageFormat, args));
+ }
+
+ ///
+ /// Throws LRS Error based on error code.
+ ///
+ /// LRS Error Code
+ public static void ThrowLRSError(LRSErrorCodes lrsErrorCode)
+ {
+ var message = string.Format(CultureInfo.CurrentCulture, "{0} - {1}", (int)lrsErrorCode, lrsErrorCode.GetString());
+ throw new ArgumentException(message);
+ }
+
+ #endregion
+
+ #region Dimensions
+
+ ///
+ /// Returns true if the input type is in the list of supported types.
+ ///
+ ///
+ ///
+ ///
+ public static bool Contains(this OpenGisGeometryType type, OpenGisGeometryType[] geometryTypes)
+ {
+ foreach (var geom in geometryTypes)
+ {
+ if (type.Equals(geom))
+ return true;
+ }
+
+ return false;
+ }
+ ///
+ /// Checks if both the segments are in same direction. either increasing or decrease based on measures.
+ ///
+ /// Geometry 1
+ /// Geometry 2
+ /// Returns true if both Geom Segments are in same direction
+ public static bool STSameDirection(this SqlGeometry geometrySegment1, SqlGeometry geometrySegment2)
+ {
+ return geometrySegment1.STLinearMeasureProgress() == geometrySegment2.STLinearMeasureProgress();
+ }
+
+ ///
+ /// Gets the dimension info of input geometry
+ ///
+ /// The SQL geometry.
+ /// Dimensional Info 2D, 2DM, 3D or 3DM
+ public static DimensionalInfo STGetDimension(this SqlGeometry sqlGeometry)
+ {
+ // return object
+ var dmDimensionalInfo = DimensionalInfo.None;
+
+ // STNumPoint can be performed only on valid geometries.
+ if (!sqlGeometry.STIsValid() || sqlGeometry.STNumPoints() <= 0)
+ {
+ dmDimensionalInfo = DimensionalInfo.None;
+ }
+ else
+ {
+ var firstPoint = sqlGeometry.STPointN(1);
+ if (firstPoint.Z.IsNull && firstPoint.M.IsNull)
+ dmDimensionalInfo = DimensionalInfo.Dim2D;
+
+ else if (firstPoint.Z.IsNull && !firstPoint.M.IsNull)
+ dmDimensionalInfo = DimensionalInfo.Dim2DWithMeasure;
+
+ else if (!firstPoint.Z.IsNull && firstPoint.M.IsNull)
+ dmDimensionalInfo = DimensionalInfo.Dim3D;
+
+ else if (!firstPoint.Z.IsNull && !firstPoint.M.IsNull)
+ dmDimensionalInfo = DimensionalInfo.Dim3DWithMeasure;
+ }
+
+ return dmDimensionalInfo;
+ }
+
+ //
+ /// Gets the dimension info of input geometry
+ /// Here no way to infer 2D with measure value
+ ///
+ /// The SQL geometry in wkt format.
+ /// Dimensional Info 2D, 2DM, 3D or 3DM
+ public static DimensionalInfo GetDimension(this string wktGeometry)
+ {
+ if (string.IsNullOrEmpty(wktGeometry))
+ return DimensionalInfo.None;
+
+ var match = Regex.Match(wktGeometry, @"\w+.*?\((?.*?)[\,\)]");
+
+ if(match.Success)
+ {
+ var dimensions = match.Groups["content"].Value.Split(' ');
+ switch(dimensions.Count())
+ {
+ case 2:
+ return DimensionalInfo.Dim2D;
+ case 3:
+ return DimensionalInfo.Dim3D;
+ case 4:
+ return DimensionalInfo.Dim3DWithMeasure;
+ default:
+ return DimensionalInfo.None;
+ }
+ }
+
+ return DimensionalInfo.None;
+ }
+
+ ///
+ /// Validate and convert to LRS dimension
+ ///
+ /// Input SQL Geometry
+ internal static void ValidateLRSDimensions(ref SqlGeometry sqlGeometry)
+ {
+ var dimension = sqlGeometry.STGetDimension();
+
+ switch (dimension)
+ {
+ case DimensionalInfo.Dim3DWithMeasure:
+ case DimensionalInfo.Dim2DWithMeasure:
+ return;
+ // if dimension is of x, y and z
+ // need to convert third z co-ordinate to M for LRS
+ case DimensionalInfo.Dim3D:
+ sqlGeometry = sqlGeometry.ConvertTo2DimensionWithMeasure();
+ break;
+ case DimensionalInfo.Dim2D:
+ throw new ArgumentException(ErrorMessage.TwoDimensionalCoordinates);
+ // skip for invalid types where Dimensional information can't be inferred
+ default:
+ return;
+ }
+ }
+
+ #endregion
+
+ #region Others
+
+ ///
+ /// Checks if two double values are equal.
+ ///
+ /// The left.
+ /// The right.
+ ///
+ public static bool EqualsTo(this double left, double right)
+ {
+ return Math.Abs(left - right) < double.Epsilon;
+ }
+
+ ///
+ /// Checks if two double values are equal.
+ ///
+ /// The left.
+ /// The right.
+ ///
+ public static bool EqualsTo(this double? left, double right)
+ {
+ var leftValue = left ?? 0.0;
+ return EqualsTo(leftValue, right);
+ }
+
+ ///
+ /// Checks if two double values are equal.
+ ///
+ /// The left.
+ /// The right.
+ ///
+ public static bool EqualsTo(this double left, double? right)
+ {
+ var rightValue = right ?? 0.0;
+ return EqualsTo(left, rightValue);
+ }
+
+ ///
+ /// Checks if two double values are equal.
+ ///
+ /// The left.
+ /// The right.
+ ///
+ public static bool EqualsTo(this double? left, double? right)
+ {
+ var leftValue = left ?? 0.0;
+ var rightValue = right ?? 0.0;
+ return EqualsTo(leftValue, rightValue);
+ }
+
+ ///
+ /// Checks if two double values are not equal.
+ ///
+ /// The left.
+ /// The right.
+ ///
+ public static bool NotEqualsTo(this double left, double right)
+ {
+ return !EqualsTo(left, right);
+ }
+
+ ///
+ /// Convert Sql geometry with x,y,z to x,y,m
+ ///
+ /// Sql Geometry
+ ///
+ internal static SqlGeometry ConvertTo2DimensionWithMeasure(this SqlGeometry sqlGeometry)
+ {
+ var sqlBuilder = new ConvertXYZ2XYMGeometrySink();
+ sqlGeometry.Populate(sqlBuilder);
+ return sqlBuilder.ConstructedGeometry;
+ }
+
+ ///
+ /// Construct SqlGeometry Point from x, y, z, m values
+ ///
+ /// x Coordinate
+ /// y Coordinate
+ /// z Coordinate
+ /// Measure
+ /// Spatial Reference Identifier; Default is 4326
+ /// Sql Point Geometry
+ public static SqlGeometry GetPoint(double x, double y, double? z, double? m, int srid = Constants.DefaultSRID)
+ {
+ var zCoordinate = z == null ? "NULL" : z.ToString();
+ var geometry = string.Format(CultureInfo.CurrentCulture, Constants.PointSqlCharFormat, x, y, zCoordinate, m);
+ return SqlGeometry.STPointFromText(new SqlChars(geometry), srid);
+ }
+
+ ///
+ /// Gets the point.
+ ///
+ /// The x.
+ /// The y.
+ /// The z.
+ /// The m.
+ /// The srid.
+ ///
+ private static SqlGeometry GetPoint(SqlDouble x, SqlDouble y, SqlDouble z, SqlDouble m, SqlInt32 srid)
+ {
+ double? zCoordinate = z.IsNull ? (double?)null : z.Value;
+ double? mValue = m.IsNull ? (double?)null : m.Value;
+ return GetPoint((double)x, (double)y, zCoordinate, mValue, (int)srid);
+ }
+
+ ///
+ /// Gets the point.
+ ///
+ /// The SQL geometry.
+ ///
+ public static SqlGeometry GetPoint(SqlGeometry sqlGeometry)
+ {
+ return GetPoint(sqlGeometry.STX, sqlGeometry.STY, sqlGeometry.Z, sqlGeometry.M, sqlGeometry.STSrid);
+ }
+
+ ///
+ /// Gets the LRS multi line.
+ ///
+ /// The SQL geometry.
+ /// if set to true [do update m].
+ /// The offset m.
+ ///
+ internal static LRSMultiLine GetLRSMultiLine(this SqlGeometry sqlGeometry, bool doUpdateM, double? offsetM)
+ {
+ ThrowIfNotLineOrMultiLine(sqlGeometry);
+ // populate the input segment
+ var lrsBuilder = new BuildLRSMultiLineSink(doUpdateM, offsetM);
+ sqlGeometry.Populate(lrsBuilder);
+ return lrsBuilder.MultiLine;
+ }
+
+ ///
+ /// Gets the LRS multi line from Sql Geometry.
+ ///
+ /// The SQL geometry.
+ ///
+ internal static LRSMultiLine GetLRSMultiLine(this SqlGeometry sqlGeometry)
+ {
+ return sqlGeometry.GetLRSMultiLine(false, 0);
+ }
+
+ ///
+ /// Gets the point with updated m.
+ ///
+ /// The SQL geometry.
+ /// The updated measure.
+ ///
+ public static SqlGeometry GetPointWithUpdatedM(SqlGeometry sqlGeometry, double updatedMeasure)
+ {
+ return GetPoint(sqlGeometry.STX, sqlGeometry.STY, sqlGeometry.Z, updatedMeasure, sqlGeometry.STSrid);
+ }
+
+ ///
+ /// Gets the type of the merge.
+ ///
+ /// The geometry1.
+ /// The geometry2.
+ ///
+ public static MergeInputType GetMergeType(this SqlGeometry geometry1, SqlGeometry geometry2)
+ {
+ var isGeom1LineString = geometry1.IsLineString();
+ var isGeom1MultiLineString = geometry1.IsMultiLineString();
+
+ var isGeom2LineString = geometry2.IsLineString();
+ var isGeom2MultiLineString = geometry2.IsMultiLineString();
+
+ if (isGeom1LineString && isGeom2LineString)
+ return MergeInputType.LSLS;
+
+ if (isGeom1MultiLineString && isGeom2MultiLineString)
+ return MergeInputType.MLSMLS;
+
+ if (isGeom1LineString && isGeom2MultiLineString)
+ return MergeInputType.LSMLS;
+
+ if (isGeom1MultiLineString && isGeom2LineString)
+ return MergeInputType.MLSLS;
+
+ return MergeInputType.None;
+ }
+
+ ///
+ /// Converts WKT string to SqlGeometry object.
+ ///
+ /// geometry in WKT representation
+ /// Spatial reference identifier; Default for SQL Server 4326
+ /// SqlGeometry
+ public static SqlGeometry GetGeom(this string geomWKT, int srid = Constants.DefaultSRID)
+ {
+ return string.IsNullOrEmpty(geomWKT) ? null : SqlGeometry.STGeomFromText(new SqlChars(geomWKT), srid);
+ }
+
+ ///
+ /// Converts WKT string to SqlGeometry object.
+ ///
+ /// geometry in WKT representation
+ /// Spatial reference identifier
+ /// SqlGeometry
+ public static SqlGeometry GetGeom(this string geomWKT, SqlInt32 srid)
+ {
+ return GetGeom(geomWKT, (int)srid);
+ }
+
+ ///
+ /// Convert WKT string to SqlGeography object.
+ ///
+ /// geography in WKT string representation
+ /// Spatial reference identifier; Default for SQL Server 4326
+ /// SqlGeography
+ public static SqlGeography GetGeog(this string geogString, int srid = Constants.DefaultSRID)
+ {
+ return SqlGeography.STGeomFromText(new SqlChars(geogString), srid);
+ }
+
+ ///
+ /// Converts Polygon to Multi Linestring or LineString
+ /// Does only based on string matching and replacement.
+ ///
+ /// Input SqlGeometry
+ ///
+ public static string GetLineWKTFromPolygon(this SqlGeometry geometry)
+ {
+ var inputText = geometry.ToString();
+ Match match = Regex.Match(inputText, _polygonSegmentRegex, RegexOptions.IgnoreCase);
+
+ if (match.Success)
+ {
+ var content = match.Groups["segments"].Value;
+ var segments = Regex.Matches(content, _segmentMatchRegex);
+ if (segments.Count == 1)
+ return $"LINESTRING {content}";
+
+ if (segments.Count > 1)
+ return $"MULTILINESTRING ({content})";
+
+ return "LINESTRING EMPTY";
+ }
+ return inputText;
+ }
+
+ ///
+ /// Converts MultiPolygon to Multi linestring
+ /// Does only based on string replacement
+ ///
+ /// Input SqlGeometry
+ ///
+ public static string GetLineWKTFromMultiPolygon(this SqlGeometry geometry)
+ {
+ var inputText = geometry.ToString();
+ Match match = Regex.Match(inputText, _multipolygonSegmentRegex, RegexOptions.IgnoreCase);
+
+ if (match.Success)
+ {
+ var content = match.Groups["segments"].Value;
+ content = content.Replace("((", "(").Replace("))", ")");
+ return $"MULTILINESTRING ({content})";
+ }
+ return inputText;
+ }
+
+ ///
+ /// Converts CurvePolygon to Line representation.
+ /// Does only based on string matching and replacement.
+ ///
+ /// Input SqlGeometry
+ /// WKT format
+ public static string GetLineWKTFromCurvePolygon(this SqlGeometry geometry)
+ {
+ var geomText = geometry.ToString();
+
+ var patternCurvePolygon = @"CURVEPOLYGON\s*\((?.*)\)";
+ var regexCurvePolygon = new Regex(patternCurvePolygon, RegexOptions.IgnoreCase);
+
+ var patternCompoundCurve = @"COMPOUNDCURVE\s*\((?.*?\)\)\,?)";
+ var regexCompoundCurve = new Regex(patternCompoundCurve, RegexOptions.IgnoreCase);
+
+ var match = regexCurvePolygon.Match(geomText);
+ var curvePolygonContent = match.Groups["content"].Value;
+
+ var geomWKTBuilder = new StringBuilder();
+ var ccIterator = 1;
+
+ // compound curve
+ var ccMatches = regexCompoundCurve.Matches(curvePolygonContent);
+
+ // within compound curve
+ if (ccMatches.Count > 0)
+ {
+ // start of multi curve
+ geomWKTBuilder.Append("GEOMETRYCOLLECTION(");
+
+ // for each compound curves
+ foreach (Match ccMatch in ccMatches)
+ {
+ geomWKTBuilder.Append("COMPOUNDCURVE(");
+
+ GetCCSubComponent(ccMatch.Groups["content"].Value, ref geomWKTBuilder);
+
+ geomWKTBuilder.Append(")");
+ if (ccIterator != ccMatches.Count)
+ geomWKTBuilder.Append(", ");
+ ccIterator++;
+ }
+
+ // end of multi curve
+ geomWKTBuilder.Append(")");
+ }
+ else
+ {
+ GetMCSubComponent(curvePolygonContent, ref geomWKTBuilder);
+ }
+
+ return geomWKTBuilder.ToString();
+ }
+
+ ///
+ /// Get WKT of a Compound Curve segment
+ ///
+ /// Sub component
+ /// String builder
+ private static void GetCCSubComponent(string textToMatch, ref StringBuilder wktBuilder)
+ {
+ var patternSubComponents = @"(?\w+)?\s*\((?.*?\d)\s*?\)";
+ var regexSubComponents = new Regex(patternSubComponents, RegexOptions.IgnoreCase);
+ var subIterator = 1;
+ // inner contents of compound curve
+ var ccContents = regexSubComponents.Matches(textToMatch);
+
+ foreach (Match ccContent in ccContents)
+ {
+ var geomType = ccContent.Groups["type"].Value.ToUpper();
+ var content = ccContent.Groups["content"].Value;
+
+ wktBuilder.Append($"{geomType}({content})");
+ if (subIterator != ccContents.Count)
+ wktBuilder.Append(", ");
+ subIterator++;
+ }
+ }
+
+ ///
+ /// Get WKT of a Multi Curve segment
+ /// Since SQL Server doesn't support Multi Curve it is converted to GeometryCollection.
+ ///
+ /// Sub component
+ /// String builder
+ private static void GetMCSubComponent(string textToMatch, ref StringBuilder wktBuilder)
+ {
+ var patternSubComponents = @"(?\w+)?\s*\((?.*?\d)\s*?\)";
+ var regexSubComponents = new Regex(patternSubComponents, RegexOptions.IgnoreCase);
+ var subIterator = 1;
+ // inner contents of compound curve
+ var ccContents = regexSubComponents.Matches(textToMatch);
+
+ var types = new List>();
+
+ foreach (Match ccContent in ccContents)
+ {
+ var type = ccContent.Groups["type"];
+ var geomType = type != null && !string.IsNullOrEmpty(type.Value) ? type.Value.ToUpper() : "LINESTRING";
+ var content = ccContent.Groups["content"].Value;
+ types.Add(new KeyValuePair(geomType, content));
+ }
+
+ var isMultiLine = types.Count(e => e.Key == "LINESTRING") == types.Count && types.Count > 1;
+
+ if (types.Count > 1 && !isMultiLine)
+ wktBuilder.Append("GEOMETRYCOLLECTION(");
+
+ if (isMultiLine)
+ wktBuilder.Append("MULTILINESTRING(");
+
+ foreach (var entry in types)
+ {
+ if (isMultiLine)
+ wktBuilder.Append($"({entry.Value})");
+ else
+ wktBuilder.Append($"{entry.Key}({entry.Value})");
+
+ if (subIterator != types.Count)
+ wktBuilder.Append(", ");
+ subIterator++;
+ }
+
+ if (types.Count > 1 || isMultiLine)
+ wktBuilder.Append(")");
+ }
+
+ #endregion
+ }
+}
diff --git a/Utility/Util.cs b/src/Utility/SpatialUtil.cs
similarity index 67%
rename from Utility/Util.cs
rename to src/Utility/SpatialUtil.cs
index 81c8bf4..c09149c 100644
--- a/Utility/Util.cs
+++ b/src/Utility/SpatialUtil.cs
@@ -1,83 +1,86 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-
-using System;
-using Microsoft.SqlServer.Types;
-
-namespace SQLSpatialTools
-{
- /**
- * This class contains functions that are meant to be used internally in this library.
- */
- class Util
- {
- // Convert a SqlGeography to an X,Y,Z vector.
- public static Vector3 GeographicToCartesian(SqlGeography point)
- {
- return SphericalDegToCartesian(point.Lat.Value, point.Long.Value);
- }
-
- // Convert an X,Y,Z vector to a SqlGeography
- public static SqlGeography CartesianToGeographic(Vector3 point, int srid)
- {
- return SqlGeography.Point(LatitudeDeg(point), LongitudeDeg(point), srid);
- }
-
- // Convert a Lat/Long in degrees to an X,Y,Z vector.
- public static Vector3 SphericalDegToCartesian(double latitudeDeg, double longitudeDeg)
- {
- if (Math.Abs(latitudeDeg) > 90)
- throw new ArgumentOutOfRangeException("|latitudeDeg| > 90");
-
- double latitudeRad = ToRadians(latitudeDeg);
- double longitudeRad = ToRadians(longitudeDeg);
- double r = Math.Cos(latitudeRad);
- return new Vector3(r * Math.Cos(longitudeRad), r * Math.Sin(longitudeRad), Math.Sin(latitudeRad));
- }
-
- // Convert a Lat/Long in radians to an X,Y,Z vector.
- public static Vector3 SphericalRadToCartesian(double latitudeRad, double longitudeRad)
- {
- if (Math.Abs(latitudeRad) > Math.PI / 2)
- throw new ArgumentOutOfRangeException("|latitudeRad| > PI / 2");
-
- double r = Math.Cos(latitudeRad);
- return new Vector3(r * Math.Cos(longitudeRad), r * Math.Sin(longitudeRad), Math.Sin(latitudeRad));
- }
-
- // Returns longitude in radians given the vector.
- public static double Longitude(Vector3 p)
- {
- return Math.Atan2(p.y, p.x);
- }
-
- // Returns longitude in degrees given the vector.
- public static double LongitudeDeg(Vector3 p)
- {
- return ToDegrees(Longitude(p));
- }
-
- // Returns latitude in radians given the vector.
- public static double Latitude(Vector3 p)
- {
- return Math.Atan2(p.z, Math.Sqrt(p.x * p.x + p.y * p.y));
- }
-
- // Returns latitude in degrees given the vector.
- public static double LatitudeDeg(Vector3 p)
- {
- return ToDegrees(Latitude(p));
- }
-
- // Converts degrees to radians.
- public static double ToRadians(double a)
- {
- return a / 180 * Math.PI;
- }
-
- // Converts radians to degrees.
- public static double ToDegrees(double a)
- {
- return a * 180 / Math.PI;
- }
- }
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using SQLSpatialTools.Types;
+
+namespace SQLSpatialTools.Utility
+{
+ ///
+ /// This class contains functions that are meant to be used internally in this library.
+ ///
+ internal static class SpatialUtil
+ {
+ // Convert a SqlGeography to an X,Y,Z vector.
+ public static Vector3 GeographicToCartesian(SqlGeography point)
+ {
+ return SphericalDegToCartesian(point.Lat.Value, point.Long.Value);
+ }
+
+ // Convert an X,Y,Z vector to a SqlGeography
+ public static SqlGeography CartesianToGeographic(Vector3 point, int srid)
+ {
+ return SqlGeography.Point(LatitudeDeg(point), LongitudeDeg(point), srid);
+ }
+
+ // Convert a Lat/Long in degrees to an X,Y,Z vector.
+ public static Vector3 SphericalDegToCartesian(double latitudeDeg, double longitudeDeg)
+ {
+ if (Math.Abs(latitudeDeg) > 90)
+ throw new ArgumentOutOfRangeException(nameof(latitudeDeg),"|latitudeDeg| > 90");
+
+ var latitudeRad = ToRadians(latitudeDeg);
+ var longitudeRad = ToRadians(longitudeDeg);
+ var r = Math.Cos(latitudeRad);
+ return new Vector3(r * Math.Cos(longitudeRad), r * Math.Sin(longitudeRad), Math.Sin(latitudeRad));
+ }
+
+ // Convert a Lat/Long in radians to an X,Y,Z vector.
+ public static Vector3 SphericalRadToCartesian(double latitudeRad, double longitudeRad)
+ {
+ if (Math.Abs(latitudeRad) > Math.PI / 2)
+ throw new ArgumentOutOfRangeException(nameof(latitudeRad), "|latitudeRad| > PI / 2");
+
+ var r = Math.Cos(latitudeRad);
+ return new Vector3(r * Math.Cos(longitudeRad), r * Math.Sin(longitudeRad), Math.Sin(latitudeRad));
+ }
+
+ // Returns longitude in radians given the vector.
+ public static double Longitude(Vector3 p)
+ {
+ return Math.Atan2(p.Y, p.X);
+ }
+
+ // Returns longitude in degrees given the vector.
+ public static double LongitudeDeg(Vector3 p)
+ {
+ return ToDegrees(Longitude(p));
+ }
+
+ // Returns latitude in radians given the vector.
+ public static double Latitude(Vector3 p)
+ {
+ return Math.Atan2(p.Z, Math.Sqrt(p.X * p.X + p.Y * p.Y));
+ }
+
+ // Returns latitude in degrees given the vector.
+ public static double LatitudeDeg(Vector3 p)
+ {
+ return ToDegrees(Latitude(p));
+ }
+
+ // Converts degrees to radians.
+ public static double ToRadians(double a)
+ {
+ return a / 180 * Math.PI;
+ }
+
+ // Converts radians to degrees.
+ public static double ToDegrees(double a)
+ {
+ return a * 180 / Math.PI;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/packages.config b/src/packages.config
new file mode 100644
index 0000000..393940a
--- /dev/null
+++ b/src/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/unittest-extension/BaseUnitTest.cs b/unittest-extension/BaseUnitTest.cs
new file mode 100644
index 0000000..80ebfe5
--- /dev/null
+++ b/unittest-extension/BaseUnitTest.cs
@@ -0,0 +1,25 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace SQLSpatialTools.UnitTests.Extension
+{
+ public class BaseUnitTest
+ {
+ protected TestLogger Logger;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ Logger = new TestLogger(TestContext);
+ }
+
+ ///
+ /// Gets or sets the test context which provides
+ /// information about and functionality for the current test run.
+ ///
+ public TestContext TestContext { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Properties/AssemblyInfo.cs b/unittest-extension/Properties/AssemblyInfo.cs
similarity index 59%
rename from Properties/AssemblyInfo.cs
rename to unittest-extension/Properties/AssemblyInfo.cs
index 774cbe3..f0951b7 100644
--- a/Properties/AssemblyInfo.cs
+++ b/unittest-extension/Properties/AssemblyInfo.cs
@@ -1,36 +1,35 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("SQLSpatialTools")]
-[assembly: AssemblyDescription("A collection of tools for use with the SQL Server spatial functionality.")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("Microsoft")]
-[assembly: AssemblyProduct("SQLSpatialTools")]
-[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("ee2e7fb3-3d63-4eb8-b0a7-8b089f5e2cf3")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.1.0.0")]
-[assembly: AssemblyFileVersion("0.1.0.0")]
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SQLSpatialTools.UnitTests.Extension")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SQLSpatialTools.UnitTests.Extension")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("98efe72e-e303-4bde-b86f-5f704db3f5be")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.3.0")]
+[assembly: AssemblyFileVersion("1.0.3.0")]
diff --git a/unittest-extension/SQLSpatialTools.UnitTests.Extension.csproj b/unittest-extension/SQLSpatialTools.UnitTests.Extension.csproj
new file mode 100644
index 0000000..8eb6c9b
--- /dev/null
+++ b/unittest-extension/SQLSpatialTools.UnitTests.Extension.csproj
@@ -0,0 +1,76 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {98EFE72E-E303-4BDE-B86F-5F704DB3F5BE}
+ Library
+ Properties
+ SQLSpatialTools.UnitTests.Extension
+ SQLSpatialTools.UnitTests.Extension
+ v4.5
+ 512
+ true
+
+
+ true
+ full
+ false
+ ..\output\unittest-extension\
+ ..\output\intermediate\unittest-extension\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ ..\output\unittest-extension\
+ ..\output\intermediate\unittest-extension\
+ TRACE
+ prompt
+ 4
+
+
+ true
+
+
+ ..\SpatialTools.pfx
+
+
+
+ ..\packages\MSTest.TestFramework.1.4.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
+
+
+ ..\packages\MSTest.TestFramework.1.4.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. %0AUse NuGet Package Restore to download them. %0AFor more information, see http://go.microsoft.com/fwlink/?LinkID=322105. %0AThe missing file is {0}.
+
+
+
+
+
+
+
+ if exist "$(ProjectDir)obj" rd "$(ProjectDir)obj" /S /Q
+exit 0
+
+
\ No newline at end of file
diff --git a/unittest-extension/TestExtension.cs b/unittest-extension/TestExtension.cs
new file mode 100644
index 0000000..16bcf51
--- /dev/null
+++ b/unittest-extension/TestExtension.cs
@@ -0,0 +1,194 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Data.SqlTypes;
+using System.Diagnostics;
+using System.Text;
+using System.Text.RegularExpressions;
+using MST = Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Globalization;
+
+namespace SQLSpatialTools.UnitTests.Extension
+{
+ public static class SqlAssert
+ {
+ public static void IsTrue(SqlBoolean sqlBoolean)
+ {
+ MST.Assert.IsTrue((bool)sqlBoolean);
+ }
+
+ public static void IsFalse(SqlBoolean sqlBoolean)
+ {
+ MST.Assert.IsFalse((bool)sqlBoolean);
+ }
+
+ public static void AreEqual(SqlDouble sqlDouble, double targetValue)
+ {
+ MST.Assert.AreEqual(Math.Round((double)sqlDouble, 4), Math.Round(targetValue, 4));
+ }
+
+ public static string GetResult(this bool result)
+ {
+ return result ? "Passed" : "Failed";
+ }
+ }
+
+ public static class TestExtension
+ {
+ private const string DecimalPointMatch = @"\.0([\s\,\)])";
+
+ ///
+ /// Trim null values in the input geometry WKT.
+ ///
+ /// input geometry in WKT
+ /// Null trimmed geom text
+ public static string TrimNullValue(this string inputGeom)
+ {
+ return Regex.Replace(inputGeom, @"\s*null\s*", " ", RegexOptions.IgnoreCase);
+ }
+
+ ///
+ /// Trims the decimal precision.
+ ///
+ /// The input geom.
+ ///
+ private static string RoundOffDecimalPrecision(this string inputGeom)
+ {
+ var output = inputGeom;
+ //var matches = Regex.Matches(inputGeom, @"(\d+\.\d{5,16})", RegexOptions.Compiled);
+ var matches = Regex.Matches(inputGeom, @"(\d+\.\d+)", RegexOptions.Compiled);
+
+ foreach (Match match in matches)
+ {
+ var inputStr = match.Groups[1].Value;
+ if (double.TryParse(inputStr, out var trimValue))
+ {
+ output = output.Replace(inputStr, Math.Round(trimValue, 4).ToString(CultureInfo.InvariantCulture));
+ }
+ }
+
+ return output;
+ }
+
+ ///
+ /// Compare the two results after converting to lower and trimming space.
+ ///
+ ///
+ ///
+ ///
+ public static bool Compare(this string firstResult, string secondResult)
+ {
+ // check for null words and assign null
+ if (!string.IsNullOrEmpty(firstResult))
+ firstResult = firstResult.ToLower(CultureInfo.CurrentCulture).Trim().Equals("null", StringComparison.CurrentCulture) ? null : firstResult.Trim();
+
+ if (!string.IsNullOrEmpty(secondResult))
+ secondResult = secondResult.ToLower(CultureInfo.CurrentCulture).Trim().Equals("null", StringComparison.CurrentCulture) ? null : secondResult.Trim();
+
+ if (string.IsNullOrEmpty(firstResult) && string.IsNullOrEmpty(secondResult))
+ return true;
+ if (string.IsNullOrEmpty(firstResult) || string.IsNullOrEmpty(secondResult))
+ return false;
+
+ firstResult = firstResult.RoundOffDecimalPrecision();
+ secondResult = secondResult.RoundOffDecimalPrecision();
+ firstResult = Regex.Replace(firstResult, @"\s+", string.Empty).ToLower(CultureInfo.CurrentCulture);
+ secondResult = Regex.Replace(secondResult, @"\s+", string.Empty).ToLower(CultureInfo.CurrentCulture);
+ return firstResult.Equals(secondResult, StringComparison.CurrentCulture);
+ }
+
+ ///
+ /// Trims the decimal points in input WKT geometry.
+ ///
+ ///
+ ///
+ public static string TrimDecimalPoints(this string inputGeomWKT)
+ {
+ if (!string.IsNullOrEmpty(inputGeomWKT))
+ return Regex.Replace(inputGeomWKT, DecimalPointMatch, "$1");
+ return inputGeomWKT;
+ }
+
+ ///
+ /// Escape single quotation in input query.
+ ///
+ ///
+ ///
+ public static string EscapeQueryString(this string query)
+ {
+ return query?.Replace("'", "''");
+ }
+ }
+
+ public class TestLogger
+ {
+ private readonly MST.TestContext _testContext;
+
+ public TestLogger(MST.TestContext testContext)
+ {
+ _testContext = testContext;
+ }
+
+ public void Log(string msgFormat, params object[] args)
+ {
+ _testContext.WriteLine(string.Format(CultureInfo.CurrentCulture, msgFormat, args));
+ }
+
+ public void LogLine(string msgFormat, params object[] args)
+ {
+ var message = new StringBuilder();
+ message.AppendLine();
+ if (args != null && args.Length > 0)
+ message.AppendFormat(CultureInfo.CurrentCulture, msgFormat, args);
+ else
+ message.Append(msgFormat);
+
+ _testContext.WriteLine(message.ToString());
+ }
+
+ public void LogError(Exception ex, string errorMessage = "", params object[] args)
+ {
+ var message = new StringBuilder();
+ var trace = new StackTrace(ex, true);
+ var frame = trace.GetFrame(0);
+ message.AppendLine();
+ if (!string.IsNullOrWhiteSpace(errorMessage))
+ {
+ if (args != null && args.Length > 0)
+ message.AppendFormat(CultureInfo.CurrentCulture, errorMessage, args);
+ else
+ message.Append(errorMessage);
+ }
+
+ message.AppendLine();
+ if (frame != null)
+ {
+ message.AppendFormat(CultureInfo.CurrentCulture, "Error module: {0}", frame.GetMethod().Name);
+ message.AppendLine();
+ message.AppendFormat(CultureInfo.CurrentCulture, "File Name: {0}", frame.GetFileName());
+ message.AppendLine();
+ message.AppendFormat(CultureInfo.CurrentCulture, "Line Number: {0}", frame.GetFileLineNumber());
+ message.AppendLine();
+ }
+ message.AppendFormat(CultureInfo.CurrentCulture, "Exception: {0}", ex.Message);
+ message.AppendLine();
+ if (ex.StackTrace != null)
+ {
+ message.AppendFormat(CultureInfo.CurrentCulture, "Stack trace: {0}", ex.StackTrace);
+ message.AppendLine();
+ }
+
+ if (ex.InnerException != null)
+ {
+ message.AppendFormat(CultureInfo.CurrentCulture, "Inner Exception: {0}", ex.InnerException.Message);
+ message.AppendLine();
+ if (ex.InnerException.StackTrace != null)
+ message.AppendFormat(CultureInfo.CurrentCulture, "Inner Stack trace: {0}", ex.InnerException.StackTrace);
+ }
+
+ _testContext.WriteLine(message.ToString());
+ }
+ }
+}
diff --git a/unittest-extension/packages.config b/unittest-extension/packages.config
new file mode 100644
index 0000000..06aa648
--- /dev/null
+++ b/unittest-extension/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/unittest/Functions/GeneralFunctionTests.cs b/unittest/Functions/GeneralFunctionTests.cs
new file mode 100644
index 0000000..db952be
--- /dev/null
+++ b/unittest/Functions/GeneralFunctionTests.cs
@@ -0,0 +1,407 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SQLSpatialTools.Functions.General;
+using SQLSpatialTools.UnitTests.Extension;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.UnitTests.Functions
+{
+ public static class GeneralFunctionTests
+ {
+ [TestClass]
+ public class GeometryTests : BaseUnitTest
+ {
+ [TestMethod]
+ public void FilterArtifactsGeometryTest()
+ {
+ var geom = "GEOMETRYCOLLECTION(LINESTRING EMPTY, LINESTRING (1 1, 3 5), POINT (1 1), POLYGON ((-1 -1, -1 -5, -5 -5, -5 -1, -1 -1)))".GetGeom();
+
+ // Empty line and point should be removed
+ // short line should be removed - tolerance length
+ var shortLineTolerance = 5;
+ // Polygon inner ring with area < tolerance * polygon length
+ var polygonAreaTolerance = 1.5;
+
+ Logger.LogLine("Input Geometry: {0}", geom);
+ Logger.Log("Filtering input geometry; removing empty line string");
+ Logger.Log("points, short line of tolerance: {0}, Polygon with inner ring area tolerance: {1}", shortLineTolerance, polygonAreaTolerance);
+ var expectedGeom = "GEOMETRYCOLLECTION EMPTY".GetGeom();
+ var filteredGeom = Geometry.FilterArtifactsGeometry(geom, true, true, shortLineTolerance, polygonAreaTolerance);
+ Logger.Log("Expected converted geom: {0}", expectedGeom);
+ Logger.Log("Obtained converted geom: {0}", filteredGeom);
+ SqlAssert.IsTrue(filteredGeom.STEquals(expectedGeom));
+ }
+
+ [TestMethod]
+ public void GeomFromXYMTextTest()
+ {
+ var geomWKT = "LINESTRING (0 0 3 4, 10 0 3 4)";
+ Logger.LogLine("Converting input Geom with 3 dimension and measure : {0}", geomWKT);
+ try
+ {
+ Geometry.GeomFromXYMText(geomWKT, Constants.DefaultSRID);
+ }
+ catch (ArgumentException e)
+ {
+ Assert.AreEqual(e.Message, ErrorMessage.WKT3DOnly);
+ TestContext.WriteLine(ErrorMessage.WKT3DOnly);
+ }
+
+ geomWKT = "LINESTRING (0 0 3, 10 0 4)";
+ Logger.LogLine("Converting input Geom with 3 dimension and measure : {0}", geomWKT);
+ var expectedGeom = "LINESTRING(0 0 NULL 3, 10 0 NULL 4)".GetGeom();
+ var convertedGeom = Geometry.GeomFromXYMText(geomWKT, Constants.DefaultSRID);
+ Logger.Log("Expected converted geom: {0}", expectedGeom);
+ Logger.Log("Obtained converted geom: {0}", convertedGeom);
+ SqlAssert.IsTrue(convertedGeom.STEquals(expectedGeom));
+ }
+
+ [TestMethod]
+ public void InterpolateBetweenGeomTest()
+ {
+ var geom1 = "POINT(0 0 0 0)".GetGeom();
+ var geom2 = "POINT(10 0 0 10)".GetGeom();
+ var returnPoint = "POINT (5 0 NULL 5)".GetGeom();
+ const int distance = 5;
+ Logger.LogLine("Input Point 1:{0} Point 2:{1}", geom1, geom2);
+ Logger.Log("Interpolating at a distance of {0}", geom1, geom2, distance);
+ Logger.LogLine("Expected Point: {0}", returnPoint);
+ var sqlGeometry = Geometry.InterpolateBetweenGeom(geom1, geom2, distance);
+ Logger.Log("Obtained Point: {0}", sqlGeometry.ToString());
+ SqlAssert.IsTrue(sqlGeometry.STEquals(returnPoint));
+
+ try
+ {
+ geom1 = "LINESTRING(0 0 0 0, 1 1 1 1)".GetGeom();
+ Geometry.InterpolateBetweenGeom(geom1, geom2, distance);
+ }
+ catch (ArgumentException)
+ {
+ }
+
+ try
+ {
+ geom1 = "POINT(0 0 0 0)".GetGeom();
+ geom2 = "POINT(0 0 0 0)".GetGeom(0);
+ Geometry.InterpolateBetweenGeom(geom1, geom2, distance);
+ }
+ catch (ArgumentException)
+ {
+ }
+
+ try
+ {
+ geom1 = "POINT(0 0 0 0)".GetGeom();
+ geom2 = "POINT(0 0 0 1)".GetGeom();
+ Geometry.InterpolateBetweenGeom(geom1, geom2, 10);
+ }
+ catch (ArgumentException)
+ {
+ }
+
+ try
+ {
+ geom1 = "POINT(0 0 0 0)".GetGeom();
+ geom2 = "POINT(0 0 0 1)".GetGeom();
+ Geometry.InterpolateBetweenGeom(geom1, geom2, -5);
+ }
+ catch (ArgumentException)
+ {
+ }
+ }
+
+ [TestMethod]
+ public void LocatePointAlongGeomTest()
+ {
+ var geom = "LINESTRING (0 0, 10 0)".GetGeom();
+ Logger.Log("Input Geom : {0}", geom.ToString());
+ var returnPoint = "POINT (5 0)".GetGeom();
+ var distance = 5;
+
+ Logger.LogLine("Locating a point at distance of {0} Measure", distance);
+ var locatedPoint = Geometry.LocatePointAlongGeom(geom, distance);
+ Logger.Log("Expected point: {0}", returnPoint);
+ Logger.Log("Located point: {0} at distance of {1} Measure", locatedPoint, distance);
+ SqlAssert.IsTrue(locatedPoint.STEquals(returnPoint));
+
+ geom = "LINESTRING (0 0 0 5, 10 0 0 10)".GetGeom();
+ returnPoint = "POINT (0 0 0 5)".GetGeom();
+ locatedPoint = Geometry.LocatePointAlongGeom(geom, 0);
+ SqlAssert.IsTrue(locatedPoint.STEquals(returnPoint));
+
+ try
+ {
+ Geometry.LocatePointAlongGeom(geom, 15);
+ }
+ catch (ArgumentException) { }
+
+ try
+ {
+ geom = "POINT (0 0 0 0)".GetGeom();
+ Geometry.LocatePointAlongGeom(geom, 15);
+ }
+ catch (ArgumentException) { }
+ }
+
+ [TestMethod]
+ public void MakeValidForGeographyTest()
+ {
+ var geometry = "CURVEPOLYGON (CIRCULARSTRING (0 -4, 4 0, 0 4, -4 0, 0 -4))".GetGeom();
+ var retGeom = Geometry.MakeValidForGeography(geometry);
+ Logger.LogLine("Executing Make Valid: {0}", geometry);
+ SqlAssert.IsTrue(retGeom.STEquals(retGeom));
+
+ geometry = "LINESTRING(0 2, 1 1, 1 0, 1 1, 2 2)".GetGeom();
+ Logger.LogLine("Executing Make Valid: {0}", geometry);
+ var expectedGeom = "MULTILINESTRING ((7.1054273576010019E-15 2, 1 1, 2 2), (1 1, 1 7.1054273576010019E-15))".GetGeom();
+ retGeom = Geometry.MakeValidForGeography(geometry);
+ Logger.Log("Expected Geom: {0}", expectedGeom);
+ Logger.Log("Obtained Geom: {0}", retGeom);
+ SqlAssert.IsTrue(retGeom.STEquals(expectedGeom));
+ }
+
+ [TestMethod]
+ public void ReverseLinestringTest()
+ {
+ var geom = "LINESTRING (1 1, 5 5)".GetGeom();
+ Logger.Log("Input Geom : {0}", geom.ToString());
+
+ var endPoint = "POINT (5 5 0 0)".GetGeom();
+ var reversedLineSegment = Geometry.ReverseLinestring(geom);
+ Logger.Log("Reversed Line string : {0}", reversedLineSegment.ToString());
+ SqlAssert.IsTrue(reversedLineSegment.STStartPoint().STEquals(endPoint));
+
+ try
+ {
+ geom = "POINT (1 1)".GetGeom();
+ Geometry.ReverseLinestring(geom);
+ }
+ catch (ArgumentException)
+ {
+ }
+ }
+
+ [TestMethod]
+ public void ShiftGeometryTest()
+ {
+ // Point
+ var geom = "POINT(0 1)".GetGeom();
+ var shiftPoint = "POINT (4 5)".GetGeom();
+ double xShift = 4, yShift = 4;
+ Logger.LogLine("Input Point: {0}", geom);
+ Logger.Log("Expected Point: {0}", shiftPoint);
+ var shiftedGeom = Geometry.ShiftGeometry(geom, xShift, yShift);
+ Logger.Log("Obtained Point: {0}", shiftedGeom);
+ SqlAssert.IsTrue(shiftedGeom.STEquals(shiftPoint));
+
+ // Simple Line String
+ geom = "LINESTRING (1 1, 4 4)".GetGeom();
+ shiftPoint = "LINESTRING (11 11, 14 14)".GetGeom();
+ xShift = 10;
+ yShift = 10;
+ Logger.LogLine("Input Geom: {0}", geom);
+ Logger.Log("Expected Geom: {0}", shiftPoint);
+ shiftedGeom = Geometry.ShiftGeometry(geom, xShift, yShift);
+ Logger.Log("Obtained Point: {0}", shiftedGeom);
+ SqlAssert.IsTrue(shiftedGeom.STEquals(shiftPoint));
+
+ // Line String with multiple points
+ geom = "LINESTRING (1 1, 2 3, -1 -3, 4 -3, -2 1)".GetGeom();
+ shiftPoint = "LINESTRING (11 11, 12 13, 9 7, 14 7, 8 11)".GetGeom();
+ Logger.LogLine("Input Geom: {0}", geom);
+ Logger.Log("Expected Geom: {0}", shiftPoint);
+ shiftedGeom = Geometry.ShiftGeometry(geom, xShift, yShift);
+ Logger.Log("Obtained Point: {0}", shiftedGeom);
+ SqlAssert.IsTrue(shiftedGeom.STEquals(shiftPoint));
+
+ // Multi Line String
+ geom = "MULTILINESTRING ((1 1, 2 3), (-1 -3, 4 -3, -2 1))".GetGeom();
+ shiftPoint = "MULTILINESTRING ((11 11, 12 13), (9 7, 14 7, 8 11))".GetGeom();
+ Logger.LogLine("Input Geom: {0}", geom);
+ Logger.Log("Expected Geom: {0}", shiftPoint);
+ shiftedGeom = Geometry.ShiftGeometry(geom, xShift, yShift);
+ Logger.Log("Obtained Point: {0}", shiftedGeom);
+ SqlAssert.IsTrue(shiftedGeom.STEquals(shiftPoint));
+
+ // Polygon
+ geom = "POLYGON((1 1, 3 3, 3 1, 1 1))".GetGeom();
+ shiftPoint = "POLYGON ((11 11, 13 13, 13 11, 11 11))".GetGeom();
+ Logger.LogLine("Input Geom: {0}", geom);
+ Logger.Log("Expected Geom: {0}", shiftPoint);
+ shiftedGeom = Geometry.ShiftGeometry(geom, xShift, yShift);
+ Logger.Log("Obtained Point: {0}", shiftedGeom);
+ SqlAssert.IsTrue(shiftedGeom.STEquals(shiftPoint));
+ }
+
+ [TestMethod]
+ public void VacuousGeometryToGeographyTest()
+ {
+ var geom = "LINESTRING (0 0 1 1, 2 2 1 1)".GetGeom();
+ var expectedGeog = "LINESTRING (0 0, 2 2)".GetGeog();
+ Logger.LogLine("Input Geometry: {0}", geom);
+ Logger.Log("Expected Geography: {0}", expectedGeog);
+ var obtainedGeog = Geometry.VacuousGeometryToGeography(geom, Constants.DefaultSRID);
+ Logger.Log("Obtained Geography: {0}", obtainedGeog);
+ SqlAssert.IsTrue(obtainedGeog.STEquals(expectedGeog));
+ }
+ }
+
+ [TestClass]
+ public class GeographyTests : BaseUnitTest
+ {
+ [TestMethod]
+ public void ConvexHullGeographyFromTextTest()
+ {
+ var geomText = "LINESTRING(-122.360 47.656, -122.343 47.656)";
+ var expectedGeog = "LINESTRING (-122.343 47.655999999999992, -122.36 47.655999999999992)".GetGeog();
+ Logger.LogLine("Input Geometry: {0}", geomText);
+ var result = Geography.ConvexHullGeographyFromText(geomText, Constants.DefaultSRID);
+ Logger.LogLine("Expected result: {0}", expectedGeog);
+ Logger.LogLine("Obtained result: {0}", result);
+ SqlAssert.IsTrue(result.STEquals(expectedGeog));
+ }
+
+ [TestMethod]
+ public void ConvexHullGeographyTest()
+ {
+ var geog = "LINESTRING(-122.360 47.656, -122.343 47.656)".GetGeog();
+ var expectedGeog = "LINESTRING (-122.343 47.655999999999992, -122.36 47.655999999999992)".GetGeog();
+ Logger.LogLine("Input Geometry: {0}", geog);
+ var result = Geography.ConvexHullGeography(geog);
+ Logger.LogLine("Expected result: {0}", expectedGeog);
+ Logger.LogLine("Obtained result: {0}", result);
+ SqlAssert.IsTrue(result.STEquals(expectedGeog));
+ }
+
+ [TestMethod]
+ public void DensifyGeographyTest()
+ {
+ var geog = "LINESTRING(-5 0, 5 0)".GetGeog();
+ var expectedGeog = "LINESTRING (-5 0, -3 0, -1.0000000000000004 0, 0.99999999999999956 0, 2.9999999999999978 0, 5 0)".GetGeog();
+ Logger.LogLine("Input Geometry: {0}", geog);
+ var result = Geography.DensifyGeography(geog, 2.0);
+ Logger.LogLine("Expected result: {0}", expectedGeog);
+ Logger.LogLine("Obtained result: {0}", result);
+ SqlAssert.IsTrue(result.STEquals(expectedGeog));
+ }
+
+ [TestMethod]
+ public void FilterArtifactsGeographyTest()
+ {
+ var geog = "GEOMETRYCOLLECTION(LINESTRING EMPTY, LINESTRING (1 1, 3 5), POINT (1 1), POLYGON ((-1 -1, -1 -5, -5 -5, -5 -1, -1 -1)))".GetGeog();
+
+ // Empty line and point should be removed
+ // short line should be removed - tolerance length
+ const double shortLineTolerance = 500000.0F;
+ // Polygon inner ring with area < tolerance * polygon length
+ const double polygonAreaTolerance = 150000.0F;
+
+ Logger.LogLine("Input Geography: {0}", geog);
+ Logger.Log("Filtering input geometry; removing empty line string");
+ Logger.Log("points, short line of tolerance: {0}, Polygon with inner ring area tolerance: {1}", shortLineTolerance, polygonAreaTolerance);
+ var expectedGeog = "GEOMETRYCOLLECTION EMPTY".GetGeog();
+ var filteredGeog = Geography.FilterArtifactsGeography(geog, true, true, shortLineTolerance, polygonAreaTolerance);
+ Logger.Log("Expected converted geog: {0}", expectedGeog);
+ Logger.Log("Obtained converted geog: {0}", filteredGeog);
+ SqlAssert.IsTrue(filteredGeog.STEquals(expectedGeog));
+ }
+
+ [TestMethod]
+ public void InterpolateBetweenGeogTest()
+ {
+ var geog1 = "POINT(0 0 0 0)".GetGeog();
+ var geog2 = "POINT(10 0 0 10)".GetGeog();
+ var returnPoint = "POINT (4.7441999536520428E-05 0)".GetGeog();
+ const int distance = 5;
+ Logger.LogLine("Input Point 1:{0} Point 2:{1}", geog1, geog2);
+ Logger.Log("Interpolating at a distance of {0}", geog1, geog2, distance);
+ Logger.LogLine("Expected Point: {0}", returnPoint);
+ var sqlGeography = Geography.InterpolateBetweenGeog(geog1, geog2, distance);
+ Logger.Log("Obtained Point: {0}", sqlGeography.ToString());
+ SqlAssert.IsTrue(sqlGeography.STEquals(returnPoint));
+ }
+
+ [TestMethod]
+ public void IsValidGeographyFromGeometryTest()
+ {
+ var geom = "LINESTRING (0 0 1 1, 2 2 1 1)".GetGeom();
+ Logger.LogLine("Input Geography: {0}", geom);
+ var result = Geography.IsValidGeographyFromGeometry(geom);
+ Logger.LogLine("Expected result: true, Obtained result: {0}", result);
+ SqlAssert.IsTrue(result);
+ }
+
+ [TestMethod]
+ public void IsValidGeographyFromTextTest()
+ {
+ var geogText = "CURVEPOLYGON (CIRCULARSTRING (0 -4, 4 0, 0 4, -4 0, 0 -4)";
+ Logger.LogLine("Input Geography: {0}", geogText);
+ var result = Geography.IsValidGeographyFromText(geogText, Constants.DefaultSRID);
+ Logger.LogLine("Expected result: false, Obtained result: {0}", result);
+ SqlAssert.IsFalse(result);
+
+ geogText = "CURVEPOLYGON (CIRCULARSTRING (0 -4, 4 0, 0 4, -4 0, 0 -4))";
+ Logger.LogLine("Input Geography: {0}", geogText);
+ result = Geography.IsValidGeographyFromText(geogText, Constants.DefaultSRID);
+ Logger.LogLine("Expected result: false, Obtained result: {0}", result);
+ SqlAssert.IsTrue(result);
+ }
+
+ [TestMethod]
+ public void LocatePointAlongGeogTest()
+ {
+ var geog = "LINESTRING (0 0, 10 0)".GetGeog();
+ Logger.Log("Input Geom : {0}", geog.ToString());
+ var returnPoint = "POINT (4.7441999536520428E-05 0)".GetGeog();
+ var distance = 5;
+
+ Logger.LogLine("Locating a point at distance of {0} Measure", distance);
+ var locatedPoint = Geography.LocatePointAlongGeog(geog, distance);
+ Logger.Log("Expected point: {0}", returnPoint);
+ Logger.Log("Located point: {0} at distance of {1} Measure", locatedPoint, distance);
+ SqlAssert.IsTrue(locatedPoint.STEquals(returnPoint));
+ }
+
+ [TestMethod]
+ public void MakeValidGeographyFromGeometryTest()
+ {
+ var geom = "LINESTRING(-122.360 47.656, -122.343 47.656)".GetGeom();
+ var expectedGeog = "LINESTRING (-122.343 47.655999999999992, -122.36 47.655999999999992)".GetGeog();
+ Logger.LogLine("Input Geometry: {0}", geom);
+ var result = Geography.MakeValidGeographyFromGeometry(geom);
+ Logger.LogLine("Expected result: {0}", expectedGeog);
+ Logger.LogLine("Obtained result: {0}", result);
+ SqlAssert.IsTrue(result.STEquals(expectedGeog));
+ }
+
+ [TestMethod]
+ public void MakeValidGeographyFromTextTest()
+ {
+ var geomText = "LINESTRING(-122.360 47.656, -122.343 47.656)";
+ var expectedGeog = "LINESTRING (-122.343 47.655999999999992, -122.36 47.655999999999992)".GetGeog();
+ Logger.LogLine("Input Geometry: {0}", geomText);
+ var result = Geography.MakeValidGeographyFromText(geomText, Constants.DefaultSRID);
+ Logger.LogLine("Expected result: {0}", expectedGeog);
+ Logger.LogLine("Obtained result: {0}", result);
+ SqlAssert.IsTrue(result.STEquals(expectedGeog));
+ }
+
+ [TestMethod]
+ public void VacuousGeographyToGeometry()
+ {
+ var geog = "LINESTRING (0 0 1 1, 2 2 1 1)".GetGeog();
+ var expectedGeom = "LINESTRING (0 0, 2 2)".GetGeom();
+ Logger.LogLine("Input Geometry: {0}", geog);
+ Logger.Log("Expected Geography: {0}", expectedGeom);
+ var obtainedGeom = Geography.VacuousGeographyToGeometry(geog, Constants.DefaultSRID);
+ Logger.Log("Obtained Geography: {0}", obtainedGeom);
+ SqlAssert.IsTrue(obtainedGeom.STEquals(expectedGeom));
+ }
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/unittest/Functions/LRSFunctionTests.cs b/unittest/Functions/LRSFunctionTests.cs
new file mode 100644
index 0000000..c397456
--- /dev/null
+++ b/unittest/Functions/LRSFunctionTests.cs
@@ -0,0 +1,1135 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.SqlServer.Types;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SQLSpatialTools.Functions.LRS;
+using SQLSpatialTools.Sinks.Geometry;
+using SQLSpatialTools.UnitTests.Extension;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.UnitTests.Functions
+{
+ [TestClass]
+ public class LRSFunctionTests : BaseUnitTest
+ {
+ [TestMethod]
+ public void ClipMultiLineTest()
+ {
+ double startMeasure = 1;
+ double endMeasure = -5;
+ var geom = "MULTILINESTRING((1 1 1, 2 2 2, 3 3 3), (4 4 4, 5.125 5.125 5.125, 6 6 6), (10 10 10, 11.25 11.25 11.25, 12 12 12))".GetGeom();
+ var expected = "POINT (1 1 1)".GetGeom();
+ var result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+
+ startMeasure = 2;
+ endMeasure = 10;
+ geom = "MULTILINESTRING((1 1 1, 2 2 2, 3 3 3), (4 4 4, 5.125 5.125 5.125, 6 6 6), (10 10 10, 11.25 11.25 11.25, 12 12 12))".GetGeom();
+ expected = "MULTILINESTRING ((2.0 2.0 2.0, 3.0 3.0 3.0), (4.0 4.0 4.0, 5.125 5.125 5.125, 6.0 6.0 6.0))".GetGeom();
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+
+ startMeasure = 2;
+ endMeasure = 11.25;
+ geom = "MULTILINESTRING((1 1 1, 2 2 2, 3 3 3), (4 4 4, 5.125 5.125 5.125, 6 6 6), (10 10 10, 11.25 11.25 11.25, 12 12 12))".GetGeom();
+ expected = "MULTILINESTRING ((2 2 NULL 2, 2 2 NULL 2, 3 3 NULL 3), (4 4 NULL 4, 5.125 5.125 NULL 5.125, 6 6 NULL 6), (10 10 NULL 10, 11.25 11.25 NULL 11.25))".GetGeom();
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+
+ startMeasure = 2.6;
+ endMeasure = 11;
+ geom = "MULTILINESTRING((1 1 1, 2 2 2, 3 3 3), (4 4 4, 5.125 5.125 5.125, 6 6 6), (10 10 10, 11.25 11.25 11.25, 12 12 12))".GetGeom();
+ expected = "MULTILINESTRING ((4 4 NULL 4, 5.125 5.125 NULL 5.125, 6 6 NULL 6), (10 10 NULL 10, 11.25 11.25 NULL 11.25))".GetGeom();
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+
+ startMeasure = 2.6;
+ endMeasure = 10;
+ geom = "MULTILINESTRING((1 1 1, 2 2 2, 3 3 3), (4 4 4, 5.125 5.125 5.125, 6 6 6), (10 10 10, 11.25 11.25 11.25, 12 12 12))".GetGeom();
+ expected = "LINESTRING (4 4 NULL 4, 5.125 5.125 NULL 5.125, 6 6 NULL 6)".GetGeom();
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+ }
+
+ [TestMethod]
+ public void ClipGeometrySegmentExtensionTest()
+ {
+ double startMeasure = 0;
+ double endMeasure = 500;
+ var geom = "LINESTRING(200000 100000 0, 200500 100000 500)".GetGeom();
+ var expected = "LINESTRING (200000 100000 NULL 0, 200500 100000 NULL 500)".GetGeom();
+ var result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+
+ startMeasure = 10;
+ endMeasure = 5;
+ geom = "LINESTRING(5 10 10, 20 5 30.628, 35 10 61.257, 55 10 100)".GetGeom();
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ expected = "POINT (5 10 NULL 10)".GetGeom();
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+
+ startMeasure = 5;
+ endMeasure = 110;
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ expected = "LINESTRING (5 10 NULL 10, 20 5 NULL 30.628, 35 10 NULL 61.257, 55 10 NULL 100)".GetGeom();
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+
+ // both measures less than start measure of input geom
+ startMeasure = 5;
+ endMeasure = 9;
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, null);
+
+ // both measures greater than end measure of input geom
+ startMeasure = 110;
+ endMeasure = 120;
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, null);
+
+ startMeasure = 10;
+ endMeasure = 110;
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ expected = "LINESTRING (5 10 NULL 10, 20 5 NULL 30.628, 35 10 NULL 61.257, 55 10 NULL 100)".GetGeom();
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+
+ startMeasure = 15;
+ endMeasure = 90;
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ expected = "LINESTRING (8.63583478766725 8.7880550707775846 NULL 15, 20 5 NULL 30.628, 35 10 NULL 61.257, 49.837777146839429 10 NULL 90)".GetGeom();
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+
+ startMeasure = 100;
+ endMeasure = 200;
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ expected = "POINT (55 10 NULL 100)".GetGeom();
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+
+ startMeasure = 61.257F;
+ endMeasure = 200;
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ expected = "LINESTRING (35 10 NULL 61.257, 55 10 NULL 100)".GetGeom();
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+
+ startMeasure = 50;
+ endMeasure = 100;
+ result = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ expected = "LINESTRING (29.487087400829282 8.1623624669430939 NULL 50, 35 10 NULL 61.257, 55 10 NULL 100)".GetGeom();
+ LogClipGeomSegments(startMeasure, endMeasure, geom, result, expected);
+ }
+
+ [TestMethod]
+ public void ClipGeometrySegmentTest()
+ {
+ var geom = "MULTILINESTRING((100 100, 200 200), (3 4, 7 8, 10 10))".GetGeom();
+ Logger.Log("Input Geom : {0}", geom.ToString());
+ try
+ {
+ Geometry.ClipGeometrySegment(geom, 15, 20);
+ }
+ catch (ArgumentException e)
+ {
+ Assert.AreEqual(e.Message, ErrorMessage.TwoDimensionalCoordinates);
+ TestContext.WriteLine(ErrorMessage.TwoDimensionalCoordinates);
+ }
+
+ // line string with null z value
+ geom = "LINESTRING (10 1 NULL 10, 25 1 NULL 25 )".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+
+ // when measures are out of range; clip segment is null
+ var startMeasure = 5;
+ var endMeasure = 7;
+ Logger.Log("Clip input geom with a Start Measure: {0} and End Measure: {1}", startMeasure, endMeasure);
+ var clippedGeom = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ Assert.IsNull(clippedGeom);
+
+ // measure in between range.
+ startMeasure = 15;
+ endMeasure = 27;
+ Logger.LogLine("Clip input geom with a Start Measure: {0} and End Measure: {1}", startMeasure, endMeasure);
+ var retGeom = "LINESTRING(15 1 NULL 15, 25 1 NULL 25)".GetGeom();
+ clippedGeom = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ SqlAssert.IsTrue(retGeom.STIsValid());
+ SqlAssert.IsTrue(clippedGeom.STEquals(retGeom));
+
+ // From start to 5 point measure
+ startMeasure = 10;
+ endMeasure = 15;
+ Logger.LogLine("Clip input geom with a Start Measure: {0} and End Measure: {1}", startMeasure, endMeasure);
+
+ retGeom = "LINESTRING (10 1 NULL 10, 15 1 NULL 15 )".GetGeom();
+ clippedGeom = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ Logger.Log("Clipped Geom: {0}", clippedGeom.ToString());
+ SqlAssert.IsTrue(retGeom.STIsValid());
+ SqlAssert.IsTrue(clippedGeom.STEquals(retGeom));
+
+ // From 15 to 20
+ startMeasure = 15;
+ endMeasure = 20;
+ Logger.LogLine("Clip input geom with a Start Measure: {0} and End Measure: {1}", startMeasure, endMeasure);
+
+ retGeom = "LINESTRING (15 1 NULL 15, 20 1 NULL 20 )".GetGeom();
+ clippedGeom = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ Logger.Log("Clipped Geom: {0}", clippedGeom.ToString());
+ SqlAssert.IsTrue(retGeom.STIsValid());
+ SqlAssert.IsTrue(clippedGeom.STEquals(retGeom));
+
+ // From 20 to 25
+ startMeasure = 20;
+ endMeasure = 25;
+ Logger.LogLine("Clip input geom with a Start Measure: {0} and End Measure: {1}", startMeasure, endMeasure);
+
+ retGeom = "LINESTRING (20 1 NULL 20, 25 1 NULL 25 )".GetGeom();
+ clippedGeom = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ Logger.Log("Clipped Geom: {0}", clippedGeom.ToString());
+ SqlAssert.IsTrue(retGeom.STIsValid());
+ SqlAssert.IsTrue(clippedGeom.STEquals(retGeom));
+
+ startMeasure = 5;
+ endMeasure = 10;
+ geom = "LINESTRING(2 2 0, 2 4 2, 8 4 8, 12 4 12, 12 10 0, 8 10 22, 5 14 27)".GetGeom();
+ retGeom = "LINESTRING (5 4 NULL 5, 8 4 NULL 8, 10 4 NULL 10)".GetGeom();
+ clippedGeom = Geometry.ClipGeometrySegment(geom, startMeasure, endMeasure);
+ Logger.Log("Clipped Geom: {0}", clippedGeom.ToString());
+ SqlAssert.IsTrue(retGeom.STIsValid());
+ SqlAssert.IsTrue(clippedGeom.STEquals(retGeom));
+ }
+
+ [TestMethod]
+ public void ConvertToLrsGeomTest()
+ {
+ // 4 point line string
+ var geom = "LINESTRING (10 4 , 20 7 , 30 9 )".GetGeom();
+ Logger.Log("Input Geom : {0}", geom.ToString());
+
+ Logger.Log("ConvertToLrs Geom with null Start and End Measure");
+ var ConvertedLrsGeom = Geometry.ConvertToLrsGeom(geom,9,null);
+ Logger.Log("ConvertToLrs Geom : {0}", ConvertedLrsGeom );
+
+ // As per requirement;
+ // the default value of start point is 0 when null is specified
+ // the default value of end point is cartographic length of the segment when null is specified
+ // if the start or end measure anything is null then it returns null value
+
+ //SqlAssert.AreEqual(ConvertedLrsGeom.GetStartPointMeasure(), 10.0F);
+ //SqlAssert.AreEqual(ConvertedLrsGeom.GetEndPointMeasure(), 1.0F);
+ }
+
+ [TestMethod]
+ public void GetEndMeasureTest()
+ {
+ var endMeasureValue = 14.0F;
+ var geom = $"POINT(5.5 5 0 {endMeasureValue})".GetGeom();
+ var endMeasure = Geometry.GetEndMeasure(geom);
+ SqlAssert.AreEqual(endMeasure, endMeasureValue);
+
+ endMeasureValue = 10.0F;
+ geom = $"LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 1000 {endMeasureValue})".GetGeom();
+ endMeasure = Geometry.GetEndMeasure(geom);
+ SqlAssert.AreEqual(endMeasure, endMeasureValue);
+
+ endMeasureValue = 100.000999450684F;
+ geom = $"MULTILINESTRING((0 0 0 0, 1 1 0 0), (3 2 0 null, 5 5 2 {endMeasureValue}))".GetGeom();
+ endMeasure = Geometry.GetEndMeasure(geom);
+ SqlAssert.AreEqual(endMeasure, endMeasureValue);
+
+ try
+ {
+ geom = ("POLYGON((0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 1000, 0 0 0 0))").GetGeom();
+ Geometry.GetEndMeasure(geom);
+ Assert.Fail("Method GetGeomSegmentEndMeasureTest should not accept polygon geometric structure");
+ }
+ catch (ArgumentException e)
+ {
+ Assert.AreEqual(e.Message, ErrorMessage.LRSCompatible);
+ TestContext.WriteLine(ErrorMessage.LRSCompatible);
+ }
+
+ endMeasureValue = 10.0F;
+ geom = "GEOMETRYCOLLECTION(LINESTRING(1 1 4, 3 5 6), MULTILINESTRING((-1 -1 0, 1 -5 5, -5 5 10), (-5 -1 5, -1 -1 10)), POINT(5 6 10))".GetGeom();
+ endMeasure = Geometry.GetEndMeasure(geom);
+ SqlAssert.AreEqual(endMeasure, endMeasureValue);
+ }
+
+ [TestMethod]
+ public void GetStartMeasureTest()
+ {
+ var startMeasureValue = 14.0F;
+ var geom = $"POINT(5.5 5 1000 {startMeasureValue})".GetGeom();
+ var startMeasure = Geometry.GetStartMeasure(geom);
+ SqlAssert.AreEqual(startMeasure, startMeasureValue);
+
+ startMeasureValue = 10.0F;
+ geom = $"LINESTRING(0 0 0 {startMeasureValue}, 1 1 0 0, 3 4 0 0, 5.5 5 1000 0)".GetGeom();
+ startMeasure = Geometry.GetStartMeasure(geom);
+ SqlAssert.AreEqual(startMeasure, startMeasureValue);
+
+ startMeasureValue = 100.000999450684F;
+ geom = string.Format("MULTILINESTRING((0 0 0 {0}, 1 1 0 0), (3 2 0 null, 5 5 2 {0}))", startMeasureValue).GetGeom();
+ startMeasure = Geometry.GetStartMeasure(geom);
+ SqlAssert.AreEqual(startMeasure, startMeasureValue);
+
+ try
+ {
+ geom = ("POLYGON((0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 1000, 0 0 0 0))").GetGeom();
+ Geometry.GetStartMeasure(geom);
+ Assert.Fail("Method GetGeomSegmentStartMeasure should not accept polygon geometric structure");
+ }
+ catch (ArgumentException e)
+ {
+ Assert.AreEqual(e.Message, ErrorMessage.LRSCompatible);
+ TestContext.WriteLine(ErrorMessage.LRSCompatible);
+ }
+
+ startMeasureValue = 4.0F;
+ geom = "GEOMETRYCOLLECTION(LINESTRING(1 1 4, 3 5 6), MULTILINESTRING((-1 -1 0, 1 -5 5, -5 5 10), (-5 -1 5, -1 -1 10)), POINT(5 6 10))".GetGeom();
+ startMeasure = Geometry.GetStartMeasure(geom);
+ SqlAssert.AreEqual(startMeasure, startMeasureValue);
+ }
+
+ [TestMethod]
+ public void InterpolateBetweenGeomTest()
+ {
+ var geom1 = "POINT(0 0 0 0)".GetGeom();
+ var geom2 = "POINT(10 0 0 10)".GetGeom();
+ var returnPoint = "POINT (5 0 NULL 5)".GetGeom();
+ const int distance = 5;
+ Logger.LogLine("Input Point 1:{0} Point 2:{1}", geom1, geom2);
+ Logger.Log("Interpolating at a distance of {0}", geom1, geom2, distance);
+ Logger.LogLine("Expected Point: {0}", returnPoint.ToString());
+ var sqlGeometry = Geometry.InterpolateBetweenGeom(geom1, geom2, distance);
+ Logger.Log("Obtained Point: {0}", sqlGeometry.ToString());
+ SqlAssert.IsTrue(sqlGeometry.STEquals(returnPoint));
+ }
+
+ [TestMethod]
+ public void IsConnectedTest()
+ {
+ // test cases without considering tolerance values
+ var geom1 = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ var geom2 = "LINESTRING(0 0 0 0, 2 2 0 0)".GetGeom();
+ var tolerance = Constants.Tolerance;
+ var result = Geometry.IsConnected(geom1, geom2, tolerance);
+ SqlAssert.IsTrue(result);
+
+ // Different SRIDs Failure Test
+ try
+ {
+ geom2 = "LINESTRING(0 0 0 0, 2 2 0 0)".GetGeom(10);
+ Geometry.IsConnected(geom1, geom2, tolerance);
+ }
+ catch (ArgumentException e)
+ {
+ Assert.AreEqual(e.Message, ErrorMessage.SRIDCompatible);
+ TestContext.WriteLine(ErrorMessage.SRIDCompatible);
+ }
+
+ geom1 = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ geom2 = "LINESTRING(2 2 0 0, 0 0 0 0)".GetGeom();
+ tolerance = 0;
+ result = Geometry.IsConnected(geom1, geom2, tolerance);
+ SqlAssert.IsTrue(result);
+
+ geom1 = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ geom2 = "LINESTRING(5.5 5 0 0, 2 2 0 0)".GetGeom();
+ tolerance = 0;
+ result = Geometry.IsConnected(geom1, geom2, tolerance);
+ SqlAssert.IsTrue(result);
+
+ geom1 = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ geom2 = "LINESTRING(2 2 9 0, 5.5 5 0 0)".GetGeom();
+ tolerance = 0;
+ result = Geometry.IsConnected(geom1, geom2, tolerance);
+ SqlAssert.IsTrue(result);
+
+ // test cases with tolerance values considered
+ // here point difference is not considered; rather x2-x1 and y2-y1 is considered for tolerance
+ geom1 = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ geom2 = "LINESTRING(0.5 0.5 0 0, 2 2 0 0)".GetGeom();
+ tolerance = 0.5;
+ result = Geometry.IsConnected(geom1, geom2, tolerance);
+ SqlAssert.IsTrue(result);
+
+ geom1 = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ geom2 = "LINESTRING(0.5 0 0 0, 2 2 0 0)".GetGeom();
+ tolerance = 0.5;
+ result = Geometry.IsConnected(geom1, geom2, tolerance);
+ SqlAssert.IsTrue(result);
+
+ geom1 = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ geom2 = "LINESTRING(1.5 1.5 0 0, 2 2 0 0)".GetGeom();
+ tolerance = 0.5;
+ result = Geometry.IsConnected(geom1, geom2, tolerance);
+ SqlAssert.IsFalse(result);
+
+ geom1 = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ geom2 = "LINESTRING(0 0.5 0 0, 2 2 0 0)".GetGeom();
+ tolerance = 0.5;
+ result = Geometry.IsConnected(geom1, geom2, tolerance);
+ SqlAssert.IsTrue(result);
+
+ geom1 = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ geom2 = "LINESTRING(2 2 0 0, 0.1 0.6 0 0)".GetGeom();
+ tolerance = 0.5;
+ result = Geometry.IsConnected(geom1, geom2, tolerance);
+ SqlAssert.IsFalse(result);
+
+ geom1 = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ geom2 = "LINESTRING(2 2 0 0, 0.6 0.1 0 0)".GetGeom();
+ tolerance = 1;
+ result = Geometry.IsConnected(geom1, geom2, tolerance);
+ SqlAssert.IsTrue(result);
+
+ geom1 = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ geom2 = "LINESTRING(5 5 0 0, 2 2 0 0)".GetGeom();
+ tolerance = 0.5;
+ result = Geometry.IsConnected(geom1, geom2, tolerance);
+ SqlAssert.IsTrue(result);
+
+ geom1 = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ geom2 = "LINESTRING(2 2 9 0, 6 4.9 0 0)".GetGeom();
+ tolerance = 0.5;
+ result = Geometry.IsConnected(geom1, geom2, tolerance);
+ SqlAssert.IsTrue(result);
+
+ geom1 = "POINT(0 0 NULL 0)".GetGeom();
+ geom2 = "POINT(0 0 NULL 0)".GetGeom();
+ SqlAssert.IsTrue(Geometry.IsConnected(geom1, geom2, tolerance));
+ }
+
+ [TestMethod]
+ public void IsValidPoint()
+ {
+ var geom = "LINESTRING(0 0 0 0, 1 1 0 0, 3 4 0 0, 5.5 5 0 0)".GetGeom();
+ SqlAssert.IsFalse(Geometry.IsValidPoint(geom));
+
+ geom = "POINT(0 0 NULL 0)".GetGeom();
+ SqlAssert.IsTrue(Geometry.IsValidPoint(geom));
+
+ geom = "POINT(0 0 0 0)".GetGeom();
+ SqlAssert.IsTrue(Geometry.IsValidPoint(geom));
+
+ geom = "POINT(0 0 1)".GetGeom();
+ SqlAssert.IsTrue(Geometry.IsValidPoint(geom));
+
+ geom = "POINT(0 0 0)".GetGeom();
+ SqlAssert.IsTrue(Geometry.IsValidPoint(geom));
+
+ geom = "POINT(0 0)".GetGeom();
+ SqlAssert.IsFalse(Geometry.IsValidPoint(geom));
+ }
+
+ [TestMethod]
+ public void LocatePointAlongGeomTest()
+ {
+ var geom = "LINESTRING (0 0 0 0, 10 0 0 10)".GetGeom();
+ Logger.Log("Input Geom : {0}", geom.ToString());
+ var returnPoint = "POINT (5 0 NULL 5)".GetGeom();
+ const int distance = 5;
+
+ var locatedPoint = Geometry.LocatePointAlongGeom(geom, distance);
+ Logger.Log("Located point : {0} at distance of {1} Measure", locatedPoint.ToString(), distance);
+
+ SqlAssert.IsTrue(locatedPoint.STEquals(returnPoint));
+
+ geom = "POINT(0 0 5)".GetGeom();
+ Geometry.LocatePointAlongGeom(geom, distance);
+ }
+
+ [TestMethod]
+ public void MergeGeometrySegmentsTest()
+ {
+ var geom1 = "MULTILINESTRING((100 100 0, 200 200 100), (3 4 0, 7 8 4, 10 10 6))".GetGeom();
+ var geomBuilder = new SqlGeometryBuilder();
+ try
+ {
+ var segment1 = new LineStringMergeGeometrySink(geomBuilder, true, geom1.STNumPoints());
+ geom1.Populate(segment1);
+ }
+ catch (Exception)
+ {
+ // ignored
+ }
+
+ try
+ {
+ geom1 = "POINT(1 1 1 1)".GetGeom();
+ geomBuilder = new SqlGeometryBuilder();
+ var segment1 = new BuildMultiLineFromLinesSink(geomBuilder, 2);
+ geom1.Populate(segment1);
+ }
+ catch (Exception)
+ {
+ // ignored
+ }
+
+ try
+ {
+ geom1 = "POLYGON((1 1, 3 3, 3 1, 1 1))".GetGeom();
+ geomBuilder = new SqlGeometryBuilder();
+ var segment1 = new BuildMultiLineFromLinesSink(geomBuilder, 2);
+ geom1.Populate(segment1);
+ }
+ catch (Exception)
+ {
+ // ignored
+ }
+
+ geom1 = "MULTILINESTRING((100 100 0, 200 200 100), (3 4 0, 7 8 4, 10 10 6))".GetGeom();
+ var geom2 = "MULTILINESTRING((11 2 0, 12 4 2, 15 5 4), (5 4 4, 6 8 6, 9 11 8))".GetGeom();
+ Logger.Log("Input Geom 1: {0}", geom1.ToString());
+ Logger.Log("Input Geom 2: {0}", geom2.ToString());
+ try
+ {
+ Geometry.MergeGeometrySegments(geom1, geom2);
+ }
+ catch (ArgumentException e)
+ {
+ Assert.AreEqual(e.Message, ErrorMessage.LineStringCompatible);
+ TestContext.WriteLine(ErrorMessage.LineStringCompatible);
+ }
+
+ geom1 = "LINESTRING(10 1 NULL 10, 25 1 NULL 25)".GetGeom();
+ geom2 = "MULTILINESTRING((11 2 0, 12 4 2, 15 5 4), (5 4 4, 6 8 6, 9 11 8))".GetGeom();
+ Logger.LogLine("Input Geom 1: {0}", geom1.ToString());
+ Logger.Log("Input Geom 2: {0}", geom2.ToString());
+ try
+ {
+ Geometry.MergeGeometrySegments(geom1, geom2);
+ }
+ catch (ArgumentException e)
+ {
+ Assert.AreEqual(e.Message, ErrorMessage.LineStringCompatible);
+ TestContext.WriteLine(ErrorMessage.LineStringCompatible);
+ }
+
+ geom1 = "MULTILINESTRING((11 2 0, 12 4 2, 15 5 4), (5 4 4, 6 8 6, 9 11 8))".GetGeom();
+ geom2 = "LINESTRING(10 1 NULL 10, 25 1 NULL 25)".GetGeom();
+ Logger.LogLine("Input Geom 1: {0}", geom1.ToString());
+ Logger.Log("Input Geom 2: {0}", geom2.ToString());
+ try
+ {
+ Geometry.MergeGeometrySegments(geom1, geom2);
+ }
+ catch (ArgumentException e)
+ {
+ Assert.AreEqual(e.Message, ErrorMessage.LineStringCompatible);
+ TestContext.WriteLine(ErrorMessage.LineStringCompatible);
+ }
+
+ // offset between geometries : 0
+ geom1 = "LINESTRING (10 1 NULL 10, 25 1 NULL 25 )".GetGeom();
+ geom2 = "LINESTRING (25 1 NULL 25, 40 1 NULL 40 )".GetGeom();
+ Logger.LogLine("Input Geom 1: {0}", geom1.ToString());
+ Logger.Log("Input Geom 2: {0}", geom2.ToString());
+
+ var mergedGeom = "LINESTRING (10 1 NULL 10, 25 1 NULL 25, 40 1 NULL 40)".GetGeom();
+ var retGeom = Geometry.MergeGeometrySegments(geom1, geom2);
+ Logger.Log("Merged Geom: {0}", retGeom.ToString());
+ SqlAssert.IsTrue(retGeom.STEquals(mergedGeom));
+
+ // offset between geometries: 5
+ geom1 = "LINESTRING (10 1 NULL 10, 25 1 NULL 25 )".GetGeom();
+ geom2 = "LINESTRING (30 1 NULL 30, 40 1 NULL 40 )".GetGeom();
+ Logger.LogLine("Input Geom 1: {0}", geom1.ToString());
+ Logger.Log("Input Geom 2: {0}", geom2.ToString());
+
+ mergedGeom = "MULTILINESTRING ((10 1 NULL 10, 25 1 NULL 25), (30 1 NULL 30, 40 1 NULL 40))".GetGeom();
+ retGeom = Geometry.MergeGeometrySegments(geom1, geom2);
+ Logger.Log("Expected Geom: {0}", mergedGeom.ToString());
+ Logger.Log("Merged Geom: {0}", retGeom.ToString());
+ SqlAssert.IsTrue(retGeom.STEquals(mergedGeom));
+ SqlAssert.IsTrue(retGeom.GetEndPointMeasure().Equals(mergedGeom.GetEndPointMeasure()));
+ }
+
+ [TestMethod]
+ public void OffsetGeometrySegmentTest()
+ {
+ var geom = "LINESTRING(214250 104000 0, 214750 104050 502.494)".GetGeom();
+ var offsetGeom = "LINESTRING (214249.80099256197 104001.99007438042 0, 214749.80099256197 104051.99007438042 502.494)".GetGeom();
+ var startMeasure = 0;
+ var endMeasure = 502.494;
+ var offset = 2;
+ var tolerance = 0.5;
+ Logger.LogLine("Input Line : {0}", geom.ToString());
+ var result = Geometry.OffsetGeometrySegment(geom, startMeasure, endMeasure, offset, tolerance);
+ Logger.Log("Expected Line : {0}", offsetGeom.ToString());
+ Logger.Log("Offset Line : {0}", result.ToString());
+ SqlAssert.IsTrue(offsetGeom.STEquals(result));
+
+ geom = "LINESTRING (5 10 10, 20 5 30.628, 35 10 61.257, 55 10 100)".GetGeom();
+ offsetGeom = "LINESTRING (28.854631868795604 10.059729063044122 NULL 50, 34.675444679663244 12 NULL 61.594145747453567, 55 12 NULL 100)".GetGeom();
+ startMeasure = 50;
+ endMeasure = 100.5;
+ offset = 2;
+ tolerance = 0.5;
+ Logger.LogLine("Input Line : {0}", geom.ToString());
+ result = Geometry.OffsetGeometrySegment(geom, startMeasure, endMeasure, offset, tolerance);
+ Logger.Log("Expected Line : {0}", offsetGeom.ToString());
+ Logger.Log("Offset Line : {0}", result.ToString());
+ SqlAssert.IsTrue(offsetGeom.STEquals(result));
+ }
+
+ [TestMethod]
+ public void OffsetGeometryWithBendLinesTest()
+ {
+ var geom = "LINESTRING (5 10 10, 20 10 30.628, 20 14 61.257, 5 14 100)".GetGeom();
+ var offsetGeom = "LINESTRING (7.0867894471005748 11.412588624412061 NULL 12, 20 7.10818510677892 NULL 36.2224547311131, 34.675444679663244 12 NULL 61.257, 54.483777714683939 12 NULL 99)".GetGeom();
+ var startMeasure = 10;
+ var endMeasure = 100;
+ var offset = -2;
+ var tolerance = 0.5;
+ Logger.LogLine("Input Line : {0}", geom.ToString());
+ var result = Geometry.OffsetGeometrySegment(geom, startMeasure, endMeasure, offset, tolerance);
+ Logger.Log("Expected Line : {0}", offsetGeom.ToString());
+ Logger.Log("Offset Line : {0}", result.ToString());
+ }
+
+ [TestMethod]
+ public void OffsetGeometrySegmentCollinearPointsTest()
+ {
+ var geom = "LINESTRING (218000 104375 0, 218000 104875 500, 218000 105000 625)".GetGeom();
+ var offsetGeom = "LINESTRING (217998 104375 0, 217998 105000 625)".GetGeom();
+ var startMeasure = 0;
+ var endMeasure = 625;
+ var offset = 2;
+ var tolerance = 0.5;
+ Logger.LogLine("Input Line : {0}", geom);
+ Logger.Log("Expected Offset Line : {0}", offsetGeom);
+ var result = Geometry.OffsetGeometrySegment(geom, startMeasure, endMeasure, offset, tolerance);
+ Logger.Log("Obtained Offset Line : {0}", result);
+ SqlAssert.IsTrue(offsetGeom.STEquals(result));
+
+ geom = "LINESTRING (2 2 0, 2 4 4, 2 6 6, 2 10 10, 2 12 12, 2 13 13, 2 17 17)".GetGeom();
+ offsetGeom = "LINESTRING (0 2.0000000000000004 0, 0 13 13)".GetGeom();
+ startMeasure = 0;
+ endMeasure = 13;
+ offset = 2;
+ tolerance = 0.5;
+ Logger.LogLine("Input Line : {0}", geom);
+ Logger.Log("Expected Offset Line : {0}", offsetGeom);
+ result = Geometry.OffsetGeometrySegment(geom, startMeasure, endMeasure, offset, tolerance);
+ Logger.Log("Obtained Offset Line : {0}", result);
+ SqlAssert.IsTrue(offsetGeom.STEquals(result));
+
+ geom = "LINESTRING(2 2 0, 4 4 4, 6 6 6, 10 5 8)".GetGeom();
+ offsetGeom = "LINESTRING (0.58578643762690508 3.4142135623730949 NULL 0, 4.5857864376269051 7.4142135623730949 NULL 6)".GetGeom();
+ startMeasure = 0;
+ endMeasure = 6;
+ offset = 2;
+ tolerance = 0.5;
+ Logger.LogLine("Input Line : {0}", geom.ToString());
+ Logger.Log("Expected Offset Line : {0}", offsetGeom);
+ result = Geometry.OffsetGeometrySegment(geom, startMeasure, endMeasure, offset, tolerance);
+ Logger.Log("Obtained Offset Line : {0}", result);
+ SqlAssert.IsTrue(offsetGeom.STEquals(result));
+ }
+
+ [TestMethod]
+ public void PopulateGeometryMeasuresTest()
+ {
+ // 4 point line string
+ var geom = "LINESTRING (10 1 10 100, 15 1 10 NULL, 20 1 10 NULL, 25 1 10 250 )".GetGeom();
+ Logger.Log("Input Geom : {0}", geom.ToString());
+
+ Logger.Log("Populating Geom with null Start and End Measure");
+ var populatedGeometry = Geometry.PopulateGeometryMeasures(geom, null, null);
+ Logger.Log("Populated Geom : {0}", populatedGeometry.ToString());
+
+ // As per requirement;
+ // the default value of start point is 0 when null is specified
+ // the default value of end point is cartographic length of the segment when null is specified
+ SqlAssert.AreEqual(populatedGeometry.GetStartPointMeasure(), 0.0F);
+ SqlAssert.AreEqual(populatedGeometry.GetEndPointMeasure(), 15.0F);
+
+ double startMeasure = 10;
+ double endMeasure = 40;
+ // if the start, end measure would be non null, then this function overrides the 'M' value that has been passed
+ Logger.Log("Populating Geom with Start Measure: {0} and End Measure: {1}", startMeasure, endMeasure);
+ populatedGeometry = Geometry.PopulateGeometryMeasures(geom, startMeasure, endMeasure);
+ Logger.Log("Populated Geom : {0}", populatedGeometry.ToString());
+ Assert.AreEqual(populatedGeometry.GetStartPointMeasure(), startMeasure);
+ SqlAssert.AreEqual(populatedGeometry.STPointN(2).M, 20.0F);
+ SqlAssert.AreEqual(populatedGeometry.STPointN(3).M, 30.0F);
+ Assert.AreEqual(populatedGeometry.GetEndPointMeasure(), endMeasure);
+ }
+
+ [TestMethod]
+ public void ResetMeasureTest()
+ { // line string with null z value
+ var geom = "LINESTRING (0 0 NULL 10, 10 0 NULL 20)".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+ var expectedGeom = "LINESTRING (0 0 NULL, 10 0 NULL)".GetGeom();
+
+ var measureResetGeom = Geometry.ResetMeasure(geom);
+ Logger.Log("Expected Geom: {0}", expectedGeom);
+ Logger.Log("Obtained Geom: {0}", measureResetGeom);
+
+ SqlAssert.AreEqual(geom.GetStartPointMeasure(), 10);
+ SqlAssert.AreEqual(geom.GetEndPointMeasure(), 20);
+ SqlAssert.IsTrue(measureResetGeom.STEquals(expectedGeom));
+ SqlAssert.IsTrue(measureResetGeom.STStartPoint().M.IsNull);
+ SqlAssert.IsTrue(measureResetGeom.STEndPoint().M.IsNull);
+
+ geom = "LINESTRING (11.235 25.987 NULL 116.124, 16.78 30.897 NULL 206.35)".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+ expectedGeom = "LINESTRING (11.235 25.987, 16.78 30.897)".GetGeom();
+
+ measureResetGeom = Geometry.ResetMeasure(geom);
+ Logger.Log("Expected Geom: {0}", expectedGeom);
+ Logger.Log("Obtained Geom: {0}", measureResetGeom);
+
+ SqlAssert.AreEqual(geom.GetStartPointMeasure(), 116.124);
+ SqlAssert.AreEqual(geom.GetEndPointMeasure(), 206.35);
+ SqlAssert.IsTrue(measureResetGeom.STEquals(expectedGeom));
+ SqlAssert.IsTrue(measureResetGeom.STStartPoint().M.IsNull);
+ SqlAssert.IsTrue(measureResetGeom.STEndPoint().M.IsNull);
+ }
+
+ [TestMethod]
+ public void ReverseLinearGeometryTest()
+ {
+ // Check for Multi Line
+ var geom = "MULTILINESTRING((1 1 1,2 2 2),(3 3 3, 5 5 7))".GetGeom();
+ var reversedStartPoint = "POINT (5 5 7)".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+ var reversedGeom = Geometry.ReverseLinearGeometry(geom);
+ Logger.Log("Reversed Geom : {0}", reversedGeom);
+ SqlAssert.IsTrue(reversedGeom.STStartPoint().STEquals(reversedStartPoint));
+
+ // Multi Line with 5 line segments
+ geom = "MULTILINESTRING((1 1 1,2 2 2, 3 3 3),(4 4 4, 5 5 5, 6 6 6), (8 8 8, 9 9 9, 10 10 10), (11 11 11, 12 12 12, 13 13 13, 14 14 14))".GetGeom();
+ reversedStartPoint = "POINT (14 14 14)".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+ reversedGeom = Geometry.ReverseLinearGeometry(geom);
+ Logger.Log("Reversed Geom : {0}", reversedGeom);
+ SqlAssert.IsTrue(reversedGeom.STStartPoint().STEquals(reversedStartPoint));
+
+ // Check for Line String
+ geom = " LINESTRING(0 0 0, 10 0 40)".GetGeom();
+ reversedStartPoint = "POINT (10 0 40)".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+ reversedGeom = Geometry.ReverseLinearGeometry(geom);
+ Logger.Log("Reversed Geom : {0}", reversedGeom);
+ SqlAssert.IsTrue(reversedGeom.STStartPoint().STEquals(reversedStartPoint));
+
+ // Check for Point
+ geom = " POINT(10 0 40)".GetGeom();
+ reversedStartPoint = "POINT (10 0 40)".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+ reversedGeom = Geometry.ReverseLinearGeometry(geom);
+ Logger.Log("Reversed Geom : {0}", reversedGeom);
+ SqlAssert.IsTrue(reversedGeom.STStartPoint().STEquals(reversedStartPoint));
+ }
+
+ [TestMethod]
+ public void ReverseTranslateMeasureGeometryTest()
+ {
+ // Check for Multi Line
+ var geom = "MULTILINESTRING((1 1 1,2 2 2),(3 3 3, 5 5 7))".GetGeom();
+ var offsetMeasure = 2;
+ var reversedStartPoint = "POINT (5 5 9)".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+ var reversedGeom = Geometry.ReverseAndTranslateGeometry(geom, offsetMeasure);
+ Logger.Log("Reversed Geom : {0}", reversedGeom);
+ SqlAssert.IsTrue(reversedGeom.STStartPoint().STEquals(reversedStartPoint));
+
+ // Multi Line with 5 line segments
+ geom = "MULTILINESTRING((1 1 1,2 2 2, 3 3 3),(4 4 4, 5 5 5, 6 6 6), (8 8 8, 9 9 9, 10 10 10), (11 11 11, 12 12 12, 13 13 13, 14 14 14))".GetGeom();
+ reversedStartPoint = "POINT (14 14 16)".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+ reversedGeom = Geometry.ReverseAndTranslateGeometry(geom, offsetMeasure);
+ Logger.Log("Reversed Geom : {0}", reversedGeom);
+ SqlAssert.IsTrue(reversedGeom.STStartPoint().STEquals(reversedStartPoint));
+
+ // Check for Line String
+ geom = " LINESTRING(0 0 0, 10 0 40)".GetGeom();
+ reversedStartPoint = "POINT (10 0 42)".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+ reversedGeom = Geometry.ReverseAndTranslateGeometry(geom, offsetMeasure);
+ Logger.Log("Reversed Geom : {0}", reversedGeom);
+ SqlAssert.IsTrue(reversedGeom.STStartPoint().STEquals(reversedStartPoint));
+
+ // Check for Point
+ geom = " POINT(10 0 40)".GetGeom();
+ reversedStartPoint = "POINT (10 0 42)".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+ reversedGeom = Geometry.ReverseAndTranslateGeometry(geom, offsetMeasure);
+ Logger.Log("Reversed Geom : {0}", reversedGeom);
+ SqlAssert.IsTrue(reversedGeom.STStartPoint().STEquals(reversedStartPoint));
+ }
+
+ [TestMethod]
+ public void MultiplyGeometryMeasureTest()
+ {
+ Logger.LogLine("Multiply Geometry Measures.");
+ // Check for Multi Line
+ var geom = "MULTILINESTRING((1 1 1,2 2 2),(3 3 3, 5 5 7))".GetGeom();
+ var scaleMeasure = -2;
+ var scaledStartPoint = "POINT (1 1 2)".GetGeom();
+ Logger.LogLine("Scale Measure : {0}", scaleMeasure);
+ Logger.Log("Input Geom : {0}", geom.ToString());
+ var scaledGeom = Geometry.MultiplyGeometryMeasures(geom, scaleMeasure);
+ Logger.Log("Reversed Geom : {0}", scaledGeom);
+ SqlAssert.IsTrue(scaledGeom.STStartPoint().STEquals(scaledStartPoint));
+
+ // Check for Line String
+ scaleMeasure = -1;
+ Logger.LogLine("Scale Measure : {0}", scaleMeasure);
+
+ geom = " LINESTRING(0 0 0, 10 0 40)".GetGeom();
+ scaledStartPoint = "POINT (0 0 0)".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+ scaledGeom = Geometry.MultiplyGeometryMeasures(geom, scaleMeasure);
+ Logger.Log("Reversed Geom : {0}", scaledGeom);
+ SqlAssert.IsTrue(scaledGeom.STStartPoint().STEquals(scaledStartPoint));
+
+ // Check for Point
+ scaleMeasure = 5;
+ Logger.LogLine("Scale Measure : {0}", scaleMeasure);
+
+ geom = " POINT(10 0 40)".GetGeom();
+ scaledStartPoint = "POINT (10 0 200)".GetGeom();
+ Logger.LogLine("Input Geom : {0}", geom.ToString());
+ scaledGeom = Geometry.MultiplyGeometryMeasures(geom, scaleMeasure);
+ Logger.Log("Reversed Geom : {0}", scaledGeom);
+ SqlAssert.IsTrue(scaledGeom.STStartPoint().STEquals(scaledStartPoint));
+ }
+
+ [TestMethod]
+ public void SplitGeometryExperimentalTest()
+ {
+ var geom = "MULTILINESTRING ((2 2 2, 2 4 4), (8 4 8, 12 4 12, 12 10 29))".GetGeom();
+ Logger.LogLine("Input Geom : {0}\n-------------------------------------\n", geom.ToString());
+
+ DoSplitTest(1, geom);
+ DoSplitTest(2, geom);
+ DoSplitTest(3, geom);
+ DoSplitTest(4, geom);
+ DoSplitTest(5, geom);
+ DoSplitTest(8, geom);
+ DoSplitTest(12, geom);
+ DoSplitTest(15, geom);
+ DoSplitTest(29, geom);
+ DoSplitTest(30, geom);
+
+ geom = "POINT(2 2 7)".GetGeom();
+ Logger.LogLine("Input Geom : {0}\n-------------------------------------\n", geom.ToString());
+
+ DoSplitTest(1, geom);
+ DoSplitTest(7, geom);
+ DoSplitTest(8, geom);
+
+ geom = "LINESTRING (2 2 2, 2 4 4, 8 4 8, 12 4 12, 12 10 29)".GetGeom();
+ Logger.LogLine("Input Geom : {0}\n-------------------------------------\n", geom.ToString());
+
+ DoSplitTest(1, geom);
+ DoSplitTest(2, geom);
+ DoSplitTest(3, geom);
+ DoSplitTest(4, geom);
+ DoSplitTest(5, geom);
+ DoSplitTest(8, geom);
+ DoSplitTest(12, geom);
+ DoSplitTest(15, geom);
+ DoSplitTest(29, geom);
+ DoSplitTest(30, geom);
+ }
+
+ [TestMethod]
+ public void ScaleGeometryMeasureTest()
+ {
+ var geom = "LINESTRING (2 2 0 6, 2 4 0 2, 8 4 0 8)".GetGeom();
+ var shiftMeasure = 2;
+ try
+ {
+ Geometry.ScaleGeometrySegment(geom, 5, 25, shiftMeasure);
+ Assert.Fail("Exception not thrown when not linear.");
+ }
+ catch (ArgumentException)
+ {
+ // ignored
+ }
+
+ geom = "LINESTRING (2 2 0 6, 2 4 0 7, 8 4 0 8)".GetGeom();
+ var result = Geometry.ScaleGeometrySegment(geom, 7, 5, shiftMeasure);
+ var expected = "LINESTRING (2 2 0 9, 2 4 0 8, 8 4 0 7)".GetGeom();
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log("Expected : {0}", expected);
+ Logger.Log("Result : {0}", result);
+ SqlAssert.IsTrue(expected.STEquals(result));
+
+ geom = "LINESTRING (2 2 0 6, 2 4 0 7, 8 4 0 8)".GetGeom();
+ result = Geometry.ScaleGeometrySegment(geom, -7, -5, -5);
+ expected = "LINESTRING (2 2 0 -12, 2 4 0 -11, 8 4 0 -10)".GetGeom();
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log("Expected : {0}", expected);
+ Logger.Log("Result : {0}", result);
+ SqlAssert.IsTrue(expected.STEquals(result));
+
+ geom = "MULTILINESTRING((1 1 1,2 2 2, 3 3 3),(4 4 4, 5 5 5, 6 6 6), (8 8 8, 9 9 9, 10 10 10), (11 11 11, 12 12 12, 13 13 13, 14 14 14))".GetGeom();
+ result = Geometry.ScaleGeometrySegment(geom, 5, 20, shiftMeasure);
+ expected = "MULTILINESTRING ((1 1 0 2, 2 2 0 4, 3 3 0 5), (4 4 0 6, 5 5 0 7, 6 6 0 8), (8 8 0 10, 9 9 0 11, 10 10 0 12), (11 11 0 13, 12 12 0 14, 13 13 0 15, 14 14 0 16))".GetGeom();
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log("Expected : {0}", expected);
+ Logger.Log("Result : {0}", result);
+ SqlAssert.IsTrue(expected.STEquals(result));
+
+ geom = "POINT(2 4 6)".GetGeom();
+ shiftMeasure = 5;
+ result = Geometry.ScaleGeometrySegment(geom, 0, 25, shiftMeasure);
+ expected = "POINT (2 4 30)".GetGeom();
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log("Expected : {0}", expected);
+ Logger.Log("Result : {0}", result);
+ SqlAssert.IsTrue(expected.STEquals(result));
+
+ geom = "POINT(2 4 6)".GetGeom();
+ shiftMeasure = 5;
+ result = Geometry.ScaleGeometrySegment(geom, 25, 0, shiftMeasure);
+ expected = "POINT (2 4 5)".GetGeom();
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log("Expected : {0}", expected);
+ Logger.Log("Result : {0}", result);
+ SqlAssert.IsTrue(expected.STEquals(result));
+ }
+
+ [TestMethod]
+ public void TranslateMeasureGeometryTest()
+ {
+ var geom = "LINESTRING (2 2 6, 2 4 2, 8 4 8)".GetGeom();
+ const int translateMeasure = 2;
+ var result = Geometry.TranslateMeasure(geom, translateMeasure);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log("Result : {0}", result);
+
+ geom = "MULTILINESTRING((1 1 1,2 2 2, 3 3 3),(4 4 4, 5 5 5, 6 6 6), (8 8 8, 9 9 9, 10 10 10), (11 11 11, 12 12 12, 13 13 13, 14 14 14))".GetGeom();
+ result = Geometry.TranslateMeasure(geom, translateMeasure);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log("Result : {0}", result);
+
+ geom = "POINT(2 4 6)".GetGeom();
+ result = Geometry.TranslateMeasure(geom, translateMeasure);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log("Result : {0}", result);
+ }
+
+ [TestMethod]
+ public void ValidateLRSGeometryTest()
+ {
+ var geom = "GEOMETRYCOLLECTION(LINESTRING(1 1 1, 3 5 2))".GetGeom();
+ var result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.InvalidLRS.Value(), result);
+
+ geom = "POINT(5 6)".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.InvalidLRS.Value(), result);
+
+ geom = "LINESTRING(1 1, 3 5)".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.InvalidLRS.Value(), result);
+
+ geom = "MULTILINESTRING ((2 2, 2 4), (8 4, 12 4))".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.InvalidLRS.Value(), result);
+
+ geom = "LINESTRING (2 2 0, 2 4 2, 8 4 8, 12 4 12, 12 10 29, 8 10 22, 5 14 27)".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.InvalidLRSMeasure.Value(), result);
+
+ geom = "LINESTRING (2 2 6, 2 4 2, 8 4 8)".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.InvalidLRSMeasure.Value(), result);
+
+ geom = "LINESTRING (2 2 12, 2 4 2, 8 4 8)".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.InvalidLRSMeasure.Value(), result);
+
+ geom = "MULTILINESTRING ((2 2 2, 2 4 0), (8 4 8, 12 4 12, 12 10 29))".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.InvalidLRSMeasure.Value(), result);
+
+ geom = "MULTILINESTRING ((2 2 2, 2 4 4), (8 4 4, 12 4 2, 12 10 29))".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.InvalidLRSMeasure.Value(), result);
+
+ geom = "MULTILINESTRING((2 2 2, 2 4 4), (8 4 2, 12 4 4, 12 10 6))".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.InvalidLRSMeasure.Value(), result);
+
+ geom = "MULTILINESTRING((2 2 2, 2 4 4), (8 4 4, 12 4 4, 12 10 6))".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.ValidLRS.Value(), result);
+
+ geom = "MULTILINESTRING((2 2 2, 2 4 2), (8 4 4, 12 4 4, 12 10 6))".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.ValidLRS.Value(), result);
+
+ // For the below TRUE is seen in Oracle; but in SQL the geom is Invalid hence we are returning invalid in this case.
+ geom = "MULTILINESTRING((2 2 2, 2 2 2), (2 2 2, 2 2 2, 2 2 2))".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual(LRSErrorCodes.InvalidLRS.Value(), result);
+
+ // Valid cases
+ geom = "POINT(5 6 5)".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual("TRUE", result);
+
+ geom = "LINESTRING(1 1 3, 3 5 5)".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual("TRUE", result);
+
+ geom = "MULTILINESTRING ((2 2 1, 2 4 4), (8 4 5, 12 4 6))".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual("TRUE", result);
+
+ geom = "LINESTRING (2 2 0, 2 4 2, 8 4 8, 12 4 12, 12 10 18, 8 10 22, 5 14 27)".GetGeom();
+ result = Geometry.ValidateLRSGeometry(geom);
+ Logger.LogLine("Input : {0}", geom.ToString());
+ Logger.Log(result);
+ Assert.AreEqual("TRUE", result);
+ }
+
+ ///
+ /// Logs the clip geom segments.
+ ///
+ /// The start measure.
+ /// The end measure.
+ /// The geom.
+ /// The result.
+ /// The expected.
+ private void LogClipGeomSegments(double startMeasure, double endMeasure, SqlGeometry geom, SqlGeometry result, SqlGeometry expected)
+ {
+ Logger.LogLine("Input Clipped at measure : {0}, End Measure : {1}", startMeasure, endMeasure);
+ Logger.Log("Input Geom : {0}", geom.ToString());
+ Logger.Log("Expected : {0}", expected?.ToString());
+ Logger.Log("Clipped : {0}", result?.ToString());
+
+ if (expected == null)
+ SqlAssert.IsTrue(result == null);
+ else
+ SqlAssert.IsTrue(expected.STEquals(result));
+ }
+
+ [TestMethod]
+ public void GetMergePositionTest()
+ {
+ //EndStart connected
+ var geom1 = "LINESTRING (1 1 10, 55 55 690)".GetGeom();
+ var geom2 = "MULTILINESTRING ((54.7 55 690, 100 100 3488), (121 124 4000, 200 201 5000))".GetGeom();
+ var tolerance = 0.3;
+ var result = Geometry.GetMergePosition(geom1, geom2, tolerance);
+ Assert.AreEqual(result, MergePosition.EndStart.ToString());
+
+ //StartStart connected
+ geom1 = "MULTILINESTRING ((1 1 10, 55 55 690), (60 61 700, 71 72 800))".GetGeom();
+ geom2 = "MULTILINESTRING ((1 0.8 100, 100 100 3488), (200 201 5000, 300 301 5005))".GetGeom();
+ tolerance = 0.3;
+ result = Geometry.GetMergePosition(geom1, geom2, tolerance);
+ Assert.AreEqual(result, MergePosition.StartStart.ToString());
+
+ //StartEnd connected
+ geom1 = "LINESTRING (1 1 10, 55 55 690)".GetGeom();
+ geom2 = "LINESTRING (5 5 690, 0.71 1 1045)".GetGeom();
+ tolerance = 0.3;
+ result = Geometry.GetMergePosition(geom1, geom2, tolerance);
+ Assert.AreEqual(result, MergePosition.StartEnd.ToString());
+
+ //StartEnd connected
+ geom1 = "LINESTRING (1 1 10, 55 55 690)".GetGeom();
+ geom2 = "POINT (1 1 100)".GetGeom();
+ tolerance = 0.3;
+ result = Geometry.GetMergePosition(geom1, geom2, tolerance);
+ Assert.AreEqual(result, MergePosition.StartEnd.ToString());
+
+ //EndEnd connected
+ geom1 = "LINESTRING (1 1 10, 55 55 690)".GetGeom();
+ geom2 = "LINESTRING (5 5 690, 54.9111 55 4555)".GetGeom();
+ tolerance = 0.3;
+ result = Geometry.GetMergePosition(geom1, geom2, tolerance);
+ Assert.AreEqual(result, MergePosition.EndEnd.ToString());
+
+ //BothEnds connected
+ geom1 = "LINESTRING (1 1 10, 44 50 45, 55 55 690)".GetGeom();
+ geom2 = "LINESTRING (1 1 690, 55 54.71 3488)".GetGeom();
+ tolerance = 0.3;
+ result = Geometry.GetMergePosition(geom1, geom2, tolerance);
+ Assert.AreEqual(result, MergePosition.BothEnds.ToString());
+
+ //StartEnd connected
+ geom1 = "LINESTRING (1 1 10, 55 55 690)".GetGeom();
+ geom2 = "LINESTRING (55 5 5690, 1 0.87 3488)".GetGeom();
+ tolerance = 0.3;
+ result = Geometry.GetMergePosition(geom1, geom2, tolerance);
+ Assert.AreEqual(result, MergePosition.StartEnd.ToString());
+
+ //Not connected
+ geom1 = "LINESTRING (1 1 10, 55 55 690)".GetGeom();
+ geom2 = "LINESTRING (5 5 690, 100 100 3488)".GetGeom();
+ tolerance = 0.3;
+ result = Geometry.GetMergePosition(geom1, geom2, tolerance);
+ Assert.AreEqual("false", result);
+
+ //CrossEnds connected
+ geom1 = "LINESTRING (1 1 10, 55 55 690)".GetGeom();
+ geom2 = "LINESTRING (55 55 5690, 1 0.87 3488)".GetGeom();
+ tolerance = 0.3;
+ result = Geometry.GetMergePosition(geom1, geom2, tolerance);
+ Assert.AreEqual(result, MergePosition.CrossEnds.ToString());
+ }
+
+ ///
+ /// Does the split test.
+ ///
+ /// The measure.
+ /// The geom.
+ private void DoSplitTest(double measure, SqlGeometry geom)
+ {
+ try
+ {
+ Logger.LogLine("Splitting for measure Geom : {0}", measure);
+ Geometry.SplitGeometrySegment(geom, measure, out var geomSegment1, out var geomSegment2);
+ Logger.Log("Segment 1 Geom : {0}", geomSegment1);
+ Logger.Log("Segment 2 Geom : {0}", geomSegment2);
+ }
+ catch (Exception ex)
+ {
+ Logger.Log("Error : {0}", ex.Message);
+ }
+ }
+ }
+}
diff --git a/unittest/Functions/UtilFunctionTests.cs b/unittest/Functions/UtilFunctionTests.cs
new file mode 100644
index 0000000..c4685cc
--- /dev/null
+++ b/unittest/Functions/UtilFunctionTests.cs
@@ -0,0 +1,557 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SQLSpatialTools.Functions.Util;
+using SQLSpatialTools.UnitTests.Extension;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.UnitTests.Functions
+{
+ [TestClass]
+ public class UtilFunctionTests
+ {
+ [TestMethod]
+ public void ExtractPointTest()
+ {
+ var geom = "POINT(1 1 1)".GetGeom();
+ var expected = "POINT(1 1 1)".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1)));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 4, 2);
+ Assert.Fail("Should through exception : Invalid index for element to be extracted.");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for element to be extracted.", ex.Message);
+ }
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 1, 2);
+ Assert.Fail("Should through exception : Invalid index for sub-element to be extracted.");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for sub-element to be extracted.", ex.Message);
+ }
+
+ geom = "MULTIPOINT((1 1 1), (2 2 2), (3 3 3), (4 4 4))".GetGeom();
+ expected = geom;
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1)));
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1, 0)));
+
+ geom = "MULTIPOINT((1 1 1), (2 2 2), (3 3 3), (4 4 4))".GetGeom();
+ expected = "POINT(1 1 1)".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1, 1)));
+
+ expected = "POINT(3 3 3)".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1, 3)));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 2, 3);
+ Assert.Fail("Should through exception : invalid index for element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for element to be extracted.", ex.Message);
+ }
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 1, 5);
+ Assert.Fail("Should through exception : invalid index for sub-element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for sub-element to be extracted.", ex.Message);
+ }
+ }
+
+ [TestMethod]
+ public void ExtractLineStringTest()
+ {
+ // LINESTRING
+ var geom = "LINESTRING(1 1 1, 2 2 2)".GetGeom();
+ var expected = "LINESTRING(1 1 1, 2 2 2)".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1)));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 2);
+ Assert.Fail("Should through exception : invalid index for element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for element to be extracted.", ex.Message);
+ }
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 1, 2);
+ Assert.Fail("Should through exception : invalid index for sub-element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for sub-element to be extracted.", ex.Message);
+ }
+
+ // CIRCULARSTRING
+ geom = "CIRCULARSTRING(1 1, 2 0, -1 1)".GetGeom();
+ expected = "CIRCULARSTRING(1 1, 2 0, -1 1)".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1)));
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1, 0)));
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1, 1)));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 2);
+ Assert.Fail("Should through exception : invalid index for element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for element to be extracted.", ex.Message);
+ }
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 1, 2);
+ Assert.Fail("Should through exception : invalid index for sub-element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for sub-element to be extracted.", ex.Message);
+ }
+
+ // MULTILINESTRING
+ geom = "MULTILINESTRING((1 1 1, 2 2 2), (3 3 3, 4 4 4))".GetGeom();
+ expected = "LINESTRING(1 1 1, 2 2 2)".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1)));
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1, 0)));
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1, 1)));
+
+ expected = "LINESTRING(3 3 3, 4 4 4)".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 2)));
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 2, 0)));
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 2, 1)));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 3, 2);
+ Assert.Fail("Should through exception : invalid index for element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for element to be extracted.", ex.Message);
+ }
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 1, 2);
+ Assert.Fail("Should through exception : invalid index for sub-element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for sub-element to be extracted.", ex.Message);
+ }
+ }
+
+ [TestMethod]
+ public void ExtractPolygonTest()
+ {
+ // Single Polygon - Sub index is to extract the inner rings
+ var geom = "POLYGON((-5 -5, -5 5, 5 5, 5 -5, -5 -5), (0 0, 3 0, 3 3, 0 3, 0 0))".GetGeom();
+ var expected = "POLYGON((-5 -5, -5 5, 5 5, 5 -5, -5 -5), (0 0, 3 0, 3 3, 0 3, 0 0))".GetGeom();
+ var obtainedGeom = Geometry.ExtractGeometry(geom, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ geom = "POLYGON((-5 -5, -5 5, 5 5, 5 -5, -5 -5), (0 0, 3 0, 3 3, 0 3, 0 0))".GetGeom();
+ expected = "POLYGON((-5 -5, -5 5, 5 5, 5 -5, -5 -5))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ geom = "POLYGON((-5 -5, -5 5, 5 5, 5 -5, -5 -5), (0 0, 3 0, 3 3, 0 3, 0 0))".GetGeom();
+ expected = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 2);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+ // here for the interior ring; the polygon is rotated; so checking the points
+ SqlAssert.IsTrue(expected.STPointN(2).STEquals(obtainedGeom.STPointN(2)));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 2, 3);
+ Assert.Fail("Should through exception : invalid index for element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for element to be extracted.", ex.Message);
+ }
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 1, 3);
+ Assert.Fail("Should through exception : invalid index for sub-element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for sub-element to be extracted.", ex.Message);
+ }
+
+ // Multi Polygon
+ geom = "MULTIPOLYGON(((1 1, 1 -1, -1 -1, -1 1, 1 1)),((1 1, 3 1, 3 3, 1 1)))".GetGeom();
+ expected = "POLYGON((1 1, 1 -1, -1 -1, -1 1, 1 1))".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1)));
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1, 0)));
+
+ expected = "POLYGON((1 1, 3 1, 3 3, 1 1))".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 2)));
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 2, 0)));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 3);
+ Assert.Fail("Should through exception : invalid index for element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for element to be extracted.", ex.Message);
+ }
+
+ geom = "MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9)))".GetGeom();
+ expected = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1)));
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1, 0)));
+
+ expected = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0))".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1, 1)));
+
+ expected = "POLYGON((1 1, 1 2, 2 1, 1 1))".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 1, 2)));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 1, 3);
+ Assert.Fail("Should through exception : invalid index for sub-element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for sub-element to be extracted.", ex.Message);
+ }
+
+ expected = "POLYGON((9 9, 9 10, 10 9, 9 9))".GetGeom();
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 2, 0)));
+ SqlAssert.IsTrue(expected.STEquals(Geometry.ExtractGeometry(geom, 2, 1)));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 2, 2);
+ Assert.Fail("Should through exception : invalid index for sub-element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for sub-element to be extracted.", ex.Message);
+ }
+ }
+
+ [TestMethod]
+ public void ExtractCurvePolygonTest()
+ {
+ var geom = "CURVEPOLYGON (CIRCULARSTRING (3 3, 4 9, 2 3, 0 0, 3 3), (1 1, 2 2, 2 1, 1 1))".GetGeom();
+ var expected = "CURVEPOLYGON (CIRCULARSTRING (3 3, 4 9, 2 3, 0 0, 3 3), (1 1, 2 2, 2 1, 1 1))".GetGeom();
+ var obtainedGeom = Geometry.ExtractGeometry(geom, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 0);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ expected = "CURVEPOLYGON (CIRCULARSTRING (3 3, 4 9, 2 3, 0 0, 3 3))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ expected = "POLYGON ((1 1, 2 1, 2 2, 1 1))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 2);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+ // here for the interior ring; the curve polygon is rotated; so checking the points
+ SqlAssert.IsTrue(expected.STPointN(2).STEquals(obtainedGeom.STPointN(2)));
+
+ geom = "CURVEPOLYGON ((0 1, 0.5 0.5, 1 0, 0.8 0.8, 0 1), CIRCULARSTRING(0.8 0.4, 0.6 0.6, 0.2 0.9, 0.7 0.7, 0.8 0.4))".GetGeom();
+ expected = "POLYGON ((0 1, 0.5 0.5, 1 0, 0.8 0.8, 0 1))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+ // here for the exterior ring; the curve polygon shouldn't be rotated; so checking the points
+ SqlAssert.IsTrue(expected.STPointN(2).STEquals(obtainedGeom.STPointN(2)));
+
+ expected = "CURVEPOLYGON(CIRCULARSTRING(0.8 0.4, 0.7 0.7, 0.2 0.9, 0.6 0.6, 0.8 0.4))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 2);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ geom = "CURVEPOLYGON ((0 1, 0.5 0.5, 1 0, 0.8 0.8, 0 1), (0.8 0.4, 0.6 0.6, 0.2 0.9, 0.7 0.7, 0.8 0.4))".GetGeom();
+ expected = "POLYGON ((0 1, 0.5 0.5, 1 0, 0.8 0.8, 0 1))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+ // here for the exterior ring; the curve polygon shouldn't be rotated; so checking the points
+ SqlAssert.IsTrue(expected.STPointN(2).STEquals(obtainedGeom.STPointN(2)));
+
+ expected = "POLYGON ((0.8 0.4, 0.7 0.7, 0.2 0.9, 0.6 0.6, 0.8 0.4))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 2);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 2, 0);
+ Assert.Fail("Should through exception : invalid index for element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for element to be extracted.", ex.Message);
+ }
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 1, 3);
+ Assert.Fail("Should through exception : invalid index for sub-element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for sub-element to be extracted.", ex.Message);
+ }
+
+ geom = "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING (1 0, 0.7 0.7, 0 1), (0 1, 1 0)))".GetGeom();
+ expected = "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING (1 0, 0.7 0.7, 0 1), (0 1, 1 0)))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 0);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ expected = "COMPOUNDCURVE(CIRCULARSTRING (1 0, 0.7 0.7, 0 1), (0 1, 1 0))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 2, 0);
+ Assert.Fail("Should through exception : invalid index for element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for element to be extracted.", ex.Message);
+ }
+
+ geom = "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3)), COMPOUNDCURVE(CIRCULARSTRING(1 3, 3 2, 5 6, 6 3, 1 3)))".GetGeom();
+ expected = "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3)), COMPOUNDCURVE(CIRCULARSTRING(1 3, 3 2, 5 6, 6 3, 1 3)))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 0);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ expected = "COMPOUNDCURVE(CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ expected = "COMPOUNDCURVE(CIRCULARSTRING(1 3, 3 2, 5 6, 6 3, 1 3))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 2);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+ }
+
+ [TestMethod]
+ public void ExtractCompoundCurveTest()
+ {
+ var geom = "COMPOUNDCURVE(CIRCULARSTRING(1 0, 0 1, -1 0), (-1 0, 2 0))".GetGeom();
+ var expected = "COMPOUNDCURVE(CIRCULARSTRING(1 0, 0 1, -1 0), (-1 0, 2 0))".GetGeom();
+ var obtainedGeom = Geometry.ExtractGeometry(geom, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 0);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 0, 1);
+ Assert.Fail("Should through exception : invalid index for element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for element to be extracted.", ex.Message);
+ }
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 1, 3);
+ Assert.Fail("Should through exception : invalid index for sub-element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for sub-element to be extracted.", ex.Message);
+ }
+ }
+
+
+ [TestMethod]
+ public void ExtractGeometryCollectionTest()
+ {
+ var geom = "GEOMETRYCOLLECTION(LINESTRING(1 1, 2 2), COMPOUNDCURVE(CIRCULARSTRING(1 0, 0 1, -1 0), (-1 0, 2 0)))".GetGeom();
+ var expected = "LINESTRING(1 1, 2 2)".GetGeom();
+ var obtainedGeom = Geometry.ExtractGeometry(geom, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 0);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ expected = "COMPOUNDCURVE(CIRCULARSTRING(1 0, 0 1, -1 0), (-1 0, 2 0))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 2);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ obtainedGeom = Geometry.ExtractGeometry(geom, 2, 0);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ obtainedGeom = Geometry.ExtractGeometry(geom, 2, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 0, 1);
+ Assert.Fail("Should through exception : invalid index for element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for element to be extracted.", ex.Message);
+ }
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 3, 1);
+ Assert.Fail("Should through exception : invalid index for element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for element to be extracted.", ex.Message);
+ }
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 1, 2);
+ Assert.Fail("Should through exception : invalid index for sub-element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for sub-element to be extracted.", ex.Message);
+ }
+
+ try
+ {
+ Geometry.ExtractGeometry(geom, 1, 3);
+ Assert.Fail("Should through exception : invalid index for sub-element to be extracted");
+ }
+ catch (ArgumentException ex)
+ {
+ Assert.AreEqual("Invalid index for sub-element to be extracted.", ex.Message);
+ }
+
+
+ geom = "GEOMETRYCOLLECTION(MULTILINESTRING((1 1, 2 2), (4 4, 5 5, 7 7), (8 8, 9 9)), POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)))".GetGeom();
+ expected = "LINESTRING(4 4, 5 5, 7 7)".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 2);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ expected = "LINESTRING(8 8, 9 9)".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 1, 3);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ expected = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 2, 1);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ expected = "POLYGON((1 1, 1 2, 2 1, 1 1))".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 2, 2);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ geom = "GEOMETRYCOLLECTION(MULTILINESTRING((1 1, 2 2), (4 4, 5 5, 7 7), (8 8, 9 9)), POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), MULTIPOINT((1 1), (2 2), (4 4)))".GetGeom();
+
+ expected = "POINT(2 2)".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 3, 2);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+
+ expected = "POINT(4 4)".GetGeom();
+ obtainedGeom = Geometry.ExtractGeometry(geom, 3, 3);
+ SqlAssert.IsTrue(expected.STEquals(obtainedGeom));
+ }
+
+ [TestMethod]
+ public void RemoveDuplicateVerticesTest()
+ {
+ var geometry = "POINT (1 2 3)".GetGeom();
+ var expected = "POINT (1 2)".GetGeom();
+ var tolerance = 0.5;
+ var resultantGeom = Geometry.RemoveDuplicateVertices(geometry, tolerance);
+ SqlAssert.IsTrue(expected.STEquals(resultantGeom));
+
+ geometry = "MULTIPOINT ((1 2 NULL 3), (5 5 NULL 6))".GetGeom();
+ expected = "MULTIPOINT ((1 2), (5 5))".GetGeom();
+ tolerance = 0.5;
+ resultantGeom = Geometry.RemoveDuplicateVertices(geometry, tolerance);
+ SqlAssert.IsTrue(expected.STEquals(resultantGeom));
+
+ geometry = "MULTILINESTRING((1 1, 3 2, 3.2 2.2, 3 8), (6 6, 10 10))".GetGeom();
+ expected = "MULTILINESTRING ((1 1, 3.2 2.2, 3 8), (6 6, 10 10))".GetGeom();
+ tolerance = 0.5;
+ resultantGeom = Geometry.RemoveDuplicateVertices(geometry, tolerance);
+ SqlAssert.IsTrue(expected.STEquals(resultantGeom));
+
+ geometry = "POLYGON ((1 1, 1 5, 8 2, 7.8 1.8, 1 1, 1 1))".GetGeom();
+ expected = "POLYGON ((1 1, 1 5, 8 2, 1 1))".GetGeom();
+ tolerance = 0.5;
+ resultantGeom = Geometry.RemoveDuplicateVertices(geometry, tolerance);
+ SqlAssert.IsTrue(expected.STEquals(resultantGeom));
+
+ geometry = "MULTIPOLYGON (((1 1, 1 -1, -1 -1, -1 1, 1 1)), ((1 1, 3 1, 3.1 3.2, 3.3 3.5 1 3, 1 1)))".GetGeom();
+ expected = "MULTIPOLYGON (((1 1, 1 -1, -1 -1, -1 1, 1 1)), ((1 1, 3 1, 3.3 3.5, 1 1)))".GetGeom();
+ tolerance = 0.5;
+ resultantGeom = Geometry.RemoveDuplicateVertices(geometry, tolerance);
+ SqlAssert.IsTrue(expected.STEquals(resultantGeom));
+
+ geometry = "CURVEPOLYGON(CIRCULARSTRING(1 3, 3 5, 4 7, 4.2 7.3, 4.5 7.5, 7 3, 1 3))".GetGeom();
+ expected = "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (1 3, 3 5, 4 7), (4 7, 4.5 7.5), CIRCULARSTRING (4.5 7.5, 7 3, 1 3)))".GetGeom();
+ tolerance = 0.5;
+ resultantGeom = Geometry.RemoveDuplicateVertices(geometry, tolerance);
+ SqlAssert.IsTrue(expected.STEquals(resultantGeom));
+
+ geometry = "GEOMETRYCOLLECTION(LINESTRING(1 1,3 5, 3.2 5.1), POINT (2 3 NULL 1), MULTIPOLYGON(((5 5, 5 10, 10 15, 15 15, 15.4 15.4, 15 10, 5 5))))".GetGeom();
+ expected = "GEOMETRYCOLLECTION (LINESTRING (1 1, 3.2 5.1), POINT (2 3), POLYGON ((5 5, 5 10, 10 15, 15.4 15.4, 15 10, 5 5)))".GetGeom();
+ tolerance = 0.5;
+ resultantGeom = Geometry.RemoveDuplicateVertices(geometry, tolerance);
+ SqlAssert.IsTrue(expected.STEquals(resultantGeom));
+
+ //negative cases
+ try
+ {
+ geometry = "LINESTRING (1 1, 6 6, 3 3, 2 2)".GetGeom(); // linestring overlaps
+ tolerance = 0.5;
+ Geometry.RemoveDuplicateVertices(geometry, tolerance);
+ Assert.Fail("Should throw exception for the invalid overlapping geometry");
+ }
+ catch(Exception ex)
+ {
+ Assert.AreEqual(ErrorMessage.InvalidGeometry, ex.Message);
+ }
+
+ try
+ {
+ geometry = "MULTILINESTRING((1 1, 3 3, 12 12), (5 5, 5 5))".GetGeom();
+ tolerance = 0.5;
+ Geometry.RemoveDuplicateVertices(geometry, tolerance);
+ Assert.Fail("Should throw exception for invalid geometry");
+ }
+ catch (Exception ex)
+ {
+ Assert.AreEqual(ErrorMessage.InvalidGeometry, ex.Message);
+ }
+ }
+ }
+}
diff --git a/unittest/Properties/AssemblyInfo.cs b/unittest/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..b701dd3
--- /dev/null
+++ b/unittest/Properties/AssemblyInfo.cs
@@ -0,0 +1,19 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("SQLSpatialTools.UnitTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SQLSpatialTools.UnitTests")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+
+[assembly: Guid("5fcdb549-1b10-4115-9a3f-aa55584fea23")]
+
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.3.0")]
+[assembly: AssemblyFileVersion("1.0.3.0")]
diff --git a/unittest/SQLSpatialTools.UnitTests.csproj b/unittest/SQLSpatialTools.UnitTests.csproj
new file mode 100644
index 0000000..9b5b30a
--- /dev/null
+++ b/unittest/SQLSpatialTools.UnitTests.csproj
@@ -0,0 +1,104 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {5FCDB549-1B10-4115-9A3F-AA55584FEA23}
+ Library
+ Properties
+ SQLSpatialTools.UnitTests
+ SQLSpatialTools.UnitTests
+ v4.5
+ 512
+ {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 15.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
+ False
+ UnitTest
+
+
+
+
+ true
+ full
+ false
+ ..\output\unittest\
+ ..\output\intermediate\unittest
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ ..\output\unittest\
+ ..\output\intermediate\unittest
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+ true
+
+
+ ..\SpatialTools.pfx
+
+
+
+ ..\packages\Microsoft.SqlServer.Types.14.0.1016.290\lib\net40\Microsoft.SqlServer.Types.dll
+ False
+
+
+ ..\packages\MSTest.TestFramework.1.4.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
+
+
+ ..\packages\MSTest.TestFramework.1.4.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+ {09428c16-7dae-4c28-a853-e4f04dd0bedd}
+ SQLSpatialTools
+
+
+ {98EFE72E-E303-4BDE-B86F-5F704DB3F5BE}
+ SQLSpatialTools.UnitTests.Extension
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. %0AUse NuGet Package Restore to download them. %0AFor more information, see http://go.microsoft.com/fwlink/?LinkID=322105. %0AThe missing file is {0}.
+
+
+
+
+
+
+
+ if exist "$(ProjectDir)obj" rd "$(ProjectDir)obj" /S /Q
+exit 0
+
+
\ No newline at end of file
diff --git a/unittest/Utility/UtilityFunctionTests.cs b/unittest/Utility/UtilityFunctionTests.cs
new file mode 100644
index 0000000..840666e
--- /dev/null
+++ b/unittest/Utility/UtilityFunctionTests.cs
@@ -0,0 +1,452 @@
+//------------------------------------------------------------------------------
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using Microsoft.SqlServer.Types;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SQLSpatialTools.Types;
+using SQLSpatialTools.UnitTests.Extension;
+using SQLSpatialTools.Utility;
+
+namespace SQLSpatialTools.UnitTests.Utility
+{
+ [TestClass]
+ public class UtilityFunctionTests : BaseUnitTest
+ {
+ [TestMethod]
+ public void DimensionalInfoTest()
+ {
+ var geom = "POINT EMPTY".GetGeom();
+ Assert.AreEqual(DimensionalInfo.None, geom.STGetDimension());
+
+ geom = "POINT (1 1)".GetGeom();
+ Assert.AreEqual(DimensionalInfo.Dim2D, geom.STGetDimension());
+
+ geom = "POINT (1 1 null 1)".GetGeom();
+ Assert.AreEqual(DimensionalInfo.Dim2DWithMeasure, geom.STGetDimension());
+
+ geom = "POINT (1 1 1)".GetGeom();
+ Assert.AreEqual(DimensionalInfo.Dim3D, geom.STGetDimension());
+
+ geom = "POINT (1 1 1 1)".GetGeom();
+ Assert.AreEqual(DimensionalInfo.Dim3DWithMeasure, geom.STGetDimension());
+
+ Assert.AreEqual(DimensionalInfo.Dim2D.GetString(), "2 Dimensional point, with x and y");
+ }
+
+ [TestMethod]
+ public void IsWithinRangeTest()
+ {
+ var geom = "LineString (1 1 NULL -4, 4 4 NULL 4)".GetGeom();
+ double measure = -1;
+ Assert.IsTrue(measure.IsWithinRange(geom));
+
+ measure = -3;
+ Assert.IsTrue(measure.IsWithinRange(geom));
+
+ measure = -4;
+ Assert.IsTrue(measure.IsWithinRange(geom));
+
+ measure = 2;
+ Assert.IsTrue(measure.IsWithinRange(geom));
+
+ measure = 4;
+ Assert.IsTrue(measure.IsWithinRange(geom));
+
+ measure = -5;
+ Assert.IsFalse(measure.IsWithinRange(geom));
+
+ measure = -15;
+ Assert.IsFalse(measure.IsWithinRange(geom));
+
+ measure = 5;
+ Assert.IsFalse(measure.IsWithinRange(geom));
+
+ geom = "LineString (1 1 NULL 4, -4 -4 NULL -4)".GetGeom();
+ measure = -1;
+ Assert.IsTrue(measure.IsWithinRange(geom));
+
+ measure = -3;
+ Assert.IsTrue(measure.IsWithinRange(geom));
+
+ measure = -4;
+ Assert.IsTrue(measure.IsWithinRange(geom));
+
+ measure = 2;
+ Assert.IsTrue(measure.IsWithinRange(geom));
+
+ measure = 4;
+ Assert.IsTrue(measure.IsWithinRange(geom));
+
+ measure = -5;
+ Assert.IsFalse(measure.IsWithinRange(geom));
+
+ measure = -15;
+ Assert.IsFalse(measure.IsWithinRange(geom));
+
+ measure = 5;
+ Assert.IsFalse(measure.IsWithinRange(geom));
+
+ double? m = 10.0;
+ double? mStart = 11.0;
+
+ Assert.IsFalse(m.IsWithinRange(mStart, null));
+ }
+
+ [TestMethod]
+ public void CheckLRSType()
+ {
+ var geom = "GEOMETRYCOLLECTION(POINT(3 3 1), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))".GetGeom();
+ Assert.IsFalse(geom.IsLRSType());
+
+ geom =
+ "GEOMETRYCOLLECTION(LINESTRING(1 1, 3 5), MULTILINESTRING((-1 -1, 1 -5, -5 5), (-5 -1, -1 -1)), POINT(5 6))"
+ .GetGeom();
+ Assert.IsTrue(geom.IsLRSType());
+
+ geom = "LINESTRING(1 1, 3 5)".GetGeom();
+ Assert.IsTrue(geom.IsLRSType());
+
+ geom = "MULTILINESTRING((-1 -1, 1 -5, -5 5), (-5 -1, -1 -1))".GetGeom();
+ Assert.IsTrue(geom.IsLRSType());
+
+ geom = "POINT(5 6)".GetGeom();
+ Assert.IsTrue(geom.IsLRSType());
+
+ geom = "POLYGON((-1 -1, -1 -5, -5 -5, -5 -1, -1 -1))".GetGeom();
+ Assert.IsFalse(geom.IsLRSType());
+
+ geom = "CIRCULARSTRING(1 1, 2 0, 2 0, 2 0, 1 1)".GetGeom();
+ Assert.IsFalse(geom.IsLRSType());
+
+ geom = "POINT(5 6)".GetGeom();
+ Assert.IsTrue(geom.IsOfSupportedTypes(OpenGisGeometryType.Point));
+ }
+
+ [TestMethod]
+ public void LogErrorTest()
+ {
+ Logger.LogError(new Exception("TestException"));
+ Logger.LogError(new Exception("TestException"), "Test");
+ Logger.LogError(new Exception("TestException"), "Test : {0}", 1);
+ Logger.LogError(new Exception("TestException"), "Test : {0}, {1}", "Value", 1);
+ var innerException = new MyException("InnerException", "Stack trace");
+ Logger.LogError(new Exception("TestException", innerException), "Test : {0}, {1}", "Value", 1);
+ }
+
+ [TestMethod]
+ public void TrimDecimalPointsTest()
+ {
+ const string inputWKT = "";
+ var result = inputWKT.TrimDecimalPoints();
+ Assert.AreEqual(inputWKT, result);
+ }
+
+ [TestMethod]
+ public void LRSMultiLineToSqlTest()
+ {
+ // empty LRS Point check
+ var point1 = new LRSPoint("LINESTRING EMPTY".GetGeom());
+ var hashCode = point1.GetHashCode();
+ Assert.IsTrue(hashCode > 0);
+ Assert.AreEqual(point1.ToString(), "POINT (0 0 )");
+
+ // LRS Point Equality check
+ point1 = new LRSPoint("POINT(1 1 0 1)".GetGeom());
+ var point2 = new LRSPoint("POINT(1 1 0 1)".GetGeom());
+ Assert.IsTrue(point1 == point2);
+
+ // range check
+ Assert.IsTrue(point1.IsXYWithinTolerance(point2, 0.5));
+
+ point2 = new LRSPoint("POINT(2 2 0 2)".GetGeom());
+ var point3 = new LRSPoint("POINT(3 3 0 3)".GetGeom());
+ var point4 = new LRSPoint("POINT(4 4 0 4)".GetGeom());
+ var lrsPoints = new List {point1, point2, point3};
+
+ // next and previous point
+ Assert.AreEqual(LRSPoint.GetNextPoint(ref lrsPoints, point2), point3);
+ Assert.AreEqual(LRSPoint.GetNextPoint(ref lrsPoints, point4), null);
+ Assert.AreEqual(LRSPoint.GetPreviousPoint(ref lrsPoints, point2), point1);
+ Assert.AreEqual(LRSPoint.GetPreviousPoint(ref lrsPoints, point4), null);
+
+ // Multiline
+ var lrs = new LRSMultiLine(4326);
+ SqlGeometryBuilder geomBuilder = null;
+ lrs.ToSqlGeometry(ref geomBuilder);
+ lrs.BuildSqlGeometry(ref geomBuilder);
+
+ geomBuilder = new SqlGeometryBuilder();
+ lrs.ToSqlGeometry(ref geomBuilder);
+ lrs.BuildSqlGeometry(ref geomBuilder);
+
+ var lrsLine = new LRSLine(4326);
+ lrsLine.AddPoint(new LRSPoint(1, 1, 0, 1, 4326));
+ lrs.AddLine(lrsLine);
+
+ geomBuilder = new SqlGeometryBuilder();
+ lrs.ToSqlGeometry(ref geomBuilder);
+
+ var lrsLine1 = new LRSLine(4326);
+ var wkt = lrs.ToString();
+ Assert.AreEqual("MULTILINESTRING EMPTY", wkt);
+ Assert.AreEqual(lrs.GetPointAtM(10), null);
+
+ var pt = new LRSPoint(1, 1, 0, 1, 4326);
+ lrsLine1.AddPoint(pt);
+ Assert.AreEqual(lrsLine1.ToString(), "POINT (1 1 1)");
+
+ pt = new LRSPoint(2, 2, 0, 2, 4326);
+ lrsLine1.AddPoint(pt);
+
+ lrs = new LRSMultiLine(4326);
+ lrs.AddLine(lrsLine1);
+
+ wkt = lrs.ToString();
+ Assert.AreEqual("LINESTRING (1 1 1, 2 2 2)", wkt);
+
+ var lrsLine2 = new LRSLine(4326);
+
+ pt = new LRSPoint(3, 3, 0, 3, 4326);
+ Assert.AreEqual(pt.ToString(), "POINT (3 3 3)");
+ lrsLine2.AddPoint(pt);
+ pt = new LRSPoint(4, 4, 0, 4, 4326);
+ lrsLine2.AddPoint(pt);
+
+ lrs = new LRSMultiLine(4326);
+ lrs.AddLine(lrsLine1);
+ lrs.AddLine(lrsLine2);
+ geomBuilder = null;
+ lrs.BuildSqlGeometry(ref geomBuilder);
+
+ Assert.AreEqual(lrs.GetPointAtM(10), null);
+ Assert.AreEqual(lrs.GetPointAtM(2), new LRSPoint(2, 2, 0, 2, 4326));
+ wkt = lrs.ToString();
+ lrs.RemoveFirst();
+ Assert.AreEqual(lrs.Count, 1);
+ lrs.RemoveLast();
+ Assert.AreEqual(lrs.Count, 0);
+ lrs.RemoveFirst();
+ lrs.RemoveLast();
+ Assert.AreEqual("MULTILINESTRING ((1 1 1, 2 2 2), (3 3 3, 4 4 4))", wkt);
+ }
+
+ private class MyException : Exception
+ {
+ public MyException(string message, string stackTrace) : base(message)
+ {
+ StackTrace = stackTrace;
+ }
+
+ public override string StackTrace { get; }
+ }
+
+ [TestMethod]
+ public void ThrowIfMeasureIsNotInRangeTest()
+ {
+ const int measure = 10;
+ var startPoint = "POINT(1 2 0)".GetGeom();
+ var endPoint = "POINT(2 2 5)".GetGeom();
+ try
+ {
+ SpatialExtensions.ThrowIfMeasureIsNotInRange(measure, startPoint, endPoint);
+ }
+ catch (ArgumentException)
+ {
+
+ }
+ }
+
+ [TestMethod]
+ public void VectorTest()
+ {
+ var vec1 = new Vector3(1, 1, 0);
+ var vec2 = new Vector3(3, 3, 0);
+ var angle = vec1.AngleInDegrees(vec2);
+ Assert.AreEqual(angle, 38.942441268981391);
+ }
+
+ [TestMethod]
+ public void LRSLineStringTest()
+ {
+ var lrsLine = new LRSLine(4326);
+ var wkt = lrsLine.ToString();
+ Assert.AreEqual(wkt, "LINESTRING EMPTY");
+ lrsLine.AddPoint(new LRSPoint(1, 1, 0, 1, 4326));
+ lrsLine.AddPoint(new LRSPoint(2, 2, null, null, 4326));
+ lrsLine.LocatePoint(3);
+ lrsLine.AddPoint(new LRSPoint(3, 3, 0, 3, 4326), new LRSPoint(4, 4, 0, 4, 4326));
+
+ var enumerator = lrsLine.GetEnumerator();
+ enumerator.MoveNext();
+ enumerator.MoveNext();
+ enumerator.MoveNext();
+ enumerator.MoveNext();
+ var currentPoint = enumerator.Current;
+ Assert.AreEqual(currentPoint, new LRSPoint(4, 4, 0, 4, 4326));
+ try
+ {
+ enumerator.MoveNext();
+ // ReSharper disable once RedundantAssignment
+ currentPoint = enumerator.Current;
+ }
+ catch (InvalidOperationException)
+ {
+ enumerator.Reset();
+ }
+ }
+
+ [TestMethod]
+ public void STLinearMeasureProgressTest()
+ {
+ var geom = "LINESTRING EMPTY".GetGeom();
+ Assert.AreEqual(geom.STLinearMeasureProgress(), LinearMeasureProgress.None);
+
+ geom = "LINESTRING (1 1 0 1, 2 2 0 5, 3 3 0 3, 4 4 0 2, 5 5 null null)".GetGeom();
+ Assert.AreEqual(geom.STLinearMeasureProgress(), LinearMeasureProgress.None);
+
+ geom = "POINT (1 1 1 1)".GetGeom();
+ Assert.AreEqual(geom.STLinearMeasureProgress(), LinearMeasureProgress.Increasing);
+
+ geom = "LINESTRING (1 1 0 0, 2 2 0 5, 3 3 0 10)".GetGeom();
+ Assert.AreEqual(geom.STLinearMeasureProgress(), LinearMeasureProgress.Increasing);
+
+ geom = "LINESTRING (1 1 0 10, 2 2 0 5, 3 3 0 0)".GetGeom();
+ Assert.AreEqual(geom.STLinearMeasureProgress(), LinearMeasureProgress.Decreasing);
+
+ Assert.AreEqual(LinearMeasureProgress.Increasing.Value(), 1);
+ Assert.AreEqual(LinearMeasureProgress.Decreasing.Value(), 2);
+ }
+
+ [TestMethod]
+ public void GetMergeTypeTest()
+ {
+ var geom1 = "LINESTRING (1 1 1 1, 2 2 2 2)".GetGeom();
+ var geom2 = "POINT (1 1 1 1)".GetGeom();
+
+ var mergeType = geom1.GetMergeType(geom2);
+ Assert.AreEqual(MergeInputType.None, mergeType);
+ }
+
+ [TestMethod]
+ public void IsNullOrEmptyTest()
+ {
+ SqlGeometry geom = null;
+ // ReSharper disable once ExpressionIsAlwaysNull
+ Assert.AreEqual(geom.IsNullOrEmpty(), true);
+
+ geom = "LINESTRING EMPTY".GetGeom();
+ Assert.AreEqual(geom.IsNullOrEmpty(), true);
+ }
+
+ [TestMethod]
+ public void CompareGeomStringTest()
+ {
+ var geom = "POINT EMPTY".GetGeom();
+ Assert.IsFalse(geom.STGeometryType().Compare(string.Empty));
+ }
+
+ [TestMethod]
+ public void STHasLinearMeasureTest()
+ {
+ var geom = "POINT EMPTY".GetGeom();
+ Assert.IsFalse(geom.STHasLinearMeasure());
+
+ geom = "LINESTRING (1 1 0 1, 2 2 0 1)".GetGeom();
+ Assert.IsTrue(geom.STHasLinearMeasure());
+
+ geom = "LINESTRING (1 1, 2 2)".GetGeom();
+ Assert.IsFalse(geom.STHasLinearMeasure());
+ }
+
+ [TestMethod]
+ public void ThrowIfNotOfTypeTest()
+ {
+ var geom = "POINT EMPTY".GetGeom();
+ try
+ {
+ SpatialExtensions.ThrowIfNotLineOrMultiLine(geom);
+ }
+ catch (ArgumentException)
+ {
+
+ }
+
+ try
+ {
+ SpatialExtensions.ThrowIfNotLine(geom);
+ }
+ catch (ArgumentException)
+ {
+
+ }
+
+ geom = "LINESTRING EMPTY".GetGeom();
+ try
+ {
+ SpatialExtensions.ThrowIfNotPoint(geom);
+ }
+ catch (ArgumentException)
+ {
+
+ }
+
+ try
+ {
+ SpatialExtensions.ThrowException("Test : {0}", "Test Message");
+ }
+ catch (ArgumentException)
+ {
+
+ }
+ }
+
+ [TestMethod]
+ public void IsTolerableTest()
+ {
+ const double distance = 0.7;
+ Assert.IsFalse(distance.IsTolerable(0.05));
+ Assert.IsTrue(distance.IsTolerable(0.9));
+ }
+
+ [TestMethod]
+ public void EqualsToTest()
+ {
+ const double distance = 0.7;
+ Assert.IsFalse(distance.EqualsTo(0.05));
+
+ double? d1 = 0.5;
+ Assert.IsTrue(d1.EqualsTo(0.5));
+
+ double? d2 = 0.5;
+ Assert.IsFalse(distance.EqualsTo(d1));
+
+ Assert.IsTrue(d1.EqualsTo(d2));
+ }
+
+
+ [TestMethod]
+ public void GISTypesTest()
+ {
+ var geom = "MULTIPOLYGON(((1 1, 1 -1, -1 -1, -1 1, 1 1)),((1 1, 3 1, 3 3, 1 3, 1 1)))".GetGeom();
+ Assert.IsTrue(geom.IsMultiPolygon());
+
+ geom = "MULTIPOINT((2 3), (7 8 9.5))".GetGeom();
+ Assert.IsTrue(geom.IsMultiPoint());
+
+ geom = "CURVEPOLYGON(CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3))".GetGeom();
+ Assert.IsTrue(geom.IsCurvePolygon());
+
+ geom = "COMPOUNDCURVE(CIRCULARSTRING(1 1, 1 1, 1 1), (1 1, 3 5, 5 4))".GetGeom();
+ Assert.IsTrue(geom.IsCompoundCurve());
+
+ geom = "CIRCULARSTRING(1 1, 2 0, -1 1)".GetGeom();
+ Assert.IsTrue(geom.IsCircularString());
+
+ geom = "POLYGON((1 1, 3 3, 3 1, 1 1))".GetGeom();
+ Assert.IsTrue(geom.IsPolygon());
+ }
+ }
+}
diff --git a/unittest/app.config b/unittest/app.config
new file mode 100644
index 0000000..2fa6e95
--- /dev/null
+++ b/unittest/app.config
@@ -0,0 +1,3 @@
+
+
+
diff --git a/unittest/packages.config b/unittest/packages.config
new file mode 100644
index 0000000..a6f0a50
--- /dev/null
+++ b/unittest/packages.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file