How do spatial reprojection in SQL Server

7.8k views Asked by At

I am trying to do some reprojection in SQL Server, but unfortunately I can't find any solution for it. I know this is not implemented in SQL Server, so wondering if there is any shortcut for it.

Some people suggested using of external libraries such as ogr2ogr while this is not a good option for me as I need to do it on the fly. I have found a post here Transform/ Project a geometry from one SRID to another, but this one converts to lat and long and has some limited capability.

I am looking for a solution to allow me to do reprojection in any srid, very similar to ST_Transform in postgresql (postgis)

1

There are 1 answers

0
Mohsen Sichani On BEST ANSWER

Well, if you are looking for an easy option, I am afraid it is not possible. But there is a workaround for this problem. I had a similar issue a while ago, and I had to implement a CLR in .NET and then import this as an assembly in MSSQL, a bit slow, but works fine. The inputs are: the Source projection, destination projection, and geometry as text, and the output is re-projected geometry as text. Please see the result of this assembly from .Net and PostgreSQL next to each other (almost the same). enter image description here

and the input was:

     POLYGON ((1755828.2027002387 5944302.7072003055, 1755826.3549002428 5944302.8313003061, 1755825.1724002406 5944285.1574003045, 1755809.5710002393 5944286.4007003047, ......))";// geomobj.line; //"POINT (1736946.0983 5923253.9175)";

So, the solution is as follows:

1- Open .NET and create a c# library and paste the following code there (this code get each line of data, goes through each element, breaks it, extracts each long and lat, reprojects it, and replaces it)

 using Microsoft.SqlServer.Server;
 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;

public class classval
{
    public string line;
    public string src;
    public string dst;

    public classval(string line, string src, string dst)
    {
        this.line = line;
        this.src = src;
        this.dst = dst;
    }



}
public class RPG_Transform
{
    private static IEnumerable<classval> ConvertedEnumerable(string line, string src, string dst)
    {
        return new List<classval> { new classval(line, src, dst) };
    }

    [SqlFunction(FillRowMethodName = "FillRow")]
    public static IEnumerable STRPG_Transform(string Geometry, string src, string dst)
    {
        return ConvertedEnumerable(Geometry, src, dst);
    }

    private static void FillRow(Object classvalobj, out string Geometry, out string srcprj, out string dstprj)
    {

        classval geomobj = (classval)classvalobj;
        string _geometry = geomobj.line;  
        string proj4_src = geomobj.src;  
        string proj4_dst = geomobj.dst; 
        string _originalgeom = _geometry;
        _geometry = _geometry.Remove(0, _geometry.IndexOf('('));
        // _geometry = _geometry.Replace("(", "[ ");
        // _geometry = _geometry.Replace(")", " ]");
        string[] splitbycomma = _geometry.Split(',');

        foreach (var itembycomma in splitbycomma)
        {

            string tmpitem = itembycomma;
            tmpitem = tmpitem.Replace('(', ' ');
            tmpitem = tmpitem.Replace(')', ' ');
            tmpitem = tmpitem.Trim();
            string[] splitbyspace = tmpitem.Split(' ');
            for (int ibs = 0; ibs < splitbyspace.Length - 1; ibs++)
            {
                string originallonglat = splitbyspace[ibs] + " " + splitbyspace[ibs + 1];

                double[] yxval = new double[] { double.Parse(splitbyspace[ibs]), double.Parse(splitbyspace[ibs + 1]) };

                double[] z = new double[1] { 0 };




                DotSpatial.Projections.ProjectionInfo src =
                    DotSpatial.Projections.ProjectionInfo.FromProj4String(proj4_src);
                DotSpatial.Projections.ProjectionInfo trg =
                    DotSpatial.Projections.ProjectionInfo.FromProj4String(proj4_dst);

                DotSpatial.Projections.Reproject.ReprojectPoints(yxval, z, src, trg, 0, 1);

                string longlat = yxval[0] + " " + yxval[1];

                _originalgeom = _originalgeom.Replace(originallonglat, longlat);
            }
        }
        srcprj = proj4_src;
        dstprj = proj4_dst;
        Geometry = _originalgeom;
    }

}

2- Before running this, you need to install some libraries and the easiest one is using Nugget Manager as shown below.

enter image description here

enter image description here

enter image description here

3- Next you need to compile the code and get a dll, and then copy these dll (s) to a new folder, if you like you leave them in the debug folder (some of these dll are added to the projects due to dependencies) as shown belowenter image description here.

4- Enable CLR in MSSQL and then add the assembly to MSSQL server (by creating a function as shown below)

  sp_configure 'show advanced options', 1;  
 GO  
 RECONFIGURE;  
 GO  
 sp_configure 'clr enabled', 1;  
 GO  
 RECONFIGURE;  
 GO  

 ALTER DATABASE Prod SET trustworthy ON
  CREATE ASSEMBLY CLRFunctionAssem
  FROM N'E:\CLR\RPG_Transform\RPG_Transform\bin\Debug\RPG_Transform.dll'
  WITH PERMISSION_SET = UNSAFE
  GO

--DROP ASSEMBLY CLRFunctionAssem ---if you need to drop it in the future

   CREATE FUNCTION dbo.RPG_STTransform(@Geometry nvarchar(max), @src 
  nvarchar(max),@dst nvarchar(max))
    RETURNS TABLE
   ( _geom  nvarchar(max) ,srcprj  nvarchar(max) ,dstprj  nvarchar(max) 
  ) with execute as caller
  AS
   EXTERNAL NAME CLRFunctionAssem.[RPG_Transform].STRPG_Transform

5- Getting the formula for the source and destination projection. You need to get these two formulas from somewhere. For me, for getting the source projection and destination projection, I use the following website.(This solution uses Proj4j, so you need to get the equivalent of the srid from somewhere).
https://epsg.io/ as shown below:

enter image description here

and the same for destination

6- Running the sql function as shown below:

         SELECT   top 1000 pk   , geom.STAsText() as input,conv._geom as ouput, 
    geometry::STGeomFromText(conv._geom,4326)
  FROM [Prod].[dbo].DPO_Geographic_Units as a  CROSS APPLY dbo.RPG_STTransform (
  a.Geom.STAsText()--geometry as text
 ,'+proj=tmerc +lat_0=0 +lon_0=173 +k=0.9996 +x_0=1600000 +y_0=10000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs ',--source =2193
 '+proj=longlat +datum=WGS84 +no_defs'--destination =4326
 ) as conv
 where [Geom] is not null

and the input and output next to each other:

enter image description here

If you like, you can save these projection formulas in the code and use a switch case to obtain the corresponded formula for each projection, then you can pass a number (e.g. 2193) and then code finds the equivalent formula.