ThinkGeo.com    |     Documentation    |     Premium Support

Custom Coordinate Systems

Hi,


I am currently evaluating Map Suite Desktop.  We are currently using shape files and over the years have developed several custom projected coordinate systems for our clients.  We need to be able to support these moving forward, so I've been trying to figure out how to do this if we move to Map Suite and SQL Server 2008.


Let me use one of these as an example, called Lambert Conformal Conic Montana Feet.  I have figured out how to make this work with our shape files in this coordinate system.  I simply translated the string in the prj file to an equivalant string in Proj4 format.


prj file


PROJCS["Custom",GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983", SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]], PROJECTION["Lambert_Conformal_Conic"],PARAMETER["False_Easting",1968503.937007874],PARAMETER["False_Northing",0.0], PARAMETER["Central_Meridian",-109.5],PARAMETER["Standard_Parallel_1",45.0],PARAMETER["Standard_Parallel_2",49.0], PARAMETER["Latitude_Of_Origin",44.25],UNIT["Foot",0.3048]]


Proj4


+proj=lcc +lat_1=45 +lat_2=49 +lat_0=44.25 +lon_0=-109.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs


Using the Proj4 string I can successfully create a ManagedProj4Projection object and my custom coordinate system shape file layers line up just fine.


The question then becomes, How do I make this work with SQL Server 2008?  I used a utility to load one of the custom shape files into SQL Server 2008, but there is no SRID for my custom coordinate system, so I just use 0.  Since it is a projected (flat) coordinate system I'm using the Geometry data type.  When I do this I can successfully open and view the layer in Map Suite, but I cannot re-project.  When I try I get an error from MapSuite:


System.NotSupportedException was unhandled

  Message=GeometryCollection is not supported now.

  Source=MapSuiteCore

  StackTrace:

       at ThinkGeo.MapSuite.Core.Projection.x479147ab5a3dca02(Byte[] x61ddc1291c0e3204)

       at ThinkGeo.MapSuite.Core.Projection.ConvertToExternalProjection(Feature feature)

       at ThinkGeo.MapSuite.Core.FeatureSource.ConvertToExternalProjection(Feature feature)

       at ThinkGeo.MapSuite.Core.FeatureSource.ConvertToExternalProjection(IEnumerable`1 features)

       at ThinkGeo.MapSuite.Core.FeatureSource.GetFeaturesForDrawing(RectangleShape boundingBox, Double screenWidth, Double screenHeight, IEnumerable`1 returningColumnNames)

       at ThinkGeo.MapSuite.Core.FeatureLayer.DrawCore(GeoCanvas canvas, Collection`1 labelsInAllLayers)

       at ThinkGeo.MapSuite.Core.Layer.Draw(GeoCanvas canvas, Collection`1 labelsInAllLayers)

       at ThinkGeo.MapSuite.DesktopEdition.LayerOverlay.DrawCore(GeoCanvas canvas)

       at ThinkGeo.MapSuite.DesktopEdition.Overlay.MainDraw(GeoCanvas canvas)

       at ThinkGeo.MapSuite.DesktopEdition.Overlay.Draw(GeoCanvas canvas)

       at ThinkGeo.MapSuite.DesktopEdition.WinformsMap.x03e3d48bcfe7bb6c(IEnumerable`1 xa6f0db4f183189f1)

       at ThinkGeo.MapSuite.DesktopEdition.WinformsMap.xff5b27c00f9678c2(RectangleShape x178b193eec228e6e)

       at ThinkGeo.MapSuite.DesktopEdition.WinformsMap.xe3cee4adb9c72451()

       at ThinkGeo.MapSuite.DesktopEdition.WinformsMap.x9ac8c50f434f4b39(Int32 xb565f4681f05557a)

       at ThinkGeo.MapSuite.DesktopEdition.WinformsMap.OnPaint(PaintEventArgs e)

       at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer)

       at System.Windows.Forms.Control.WmPaint(Message& m)

       at System.Windows.Forms.Control.WndProc(Message& m)

       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)

       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)

       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

       at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)

       at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)

       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)

       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)

       at System.Windows.Forms.Application.Run(ApplicationContext context)

       at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.OnRun()

       at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.DoApplicationModel()

       at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.Run(String[] commandLine)

       at MapTutorial.My.MyApplication.Main(String[] Args) in 17d14f5c-a337-4978-8281-53493378c1071.vb:line 81

       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)

       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)

       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()

       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)

       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)

       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

       at System.Threading.ThreadHelper.ThreadStart()

  InnerException:


Any ideas or am I just out of luck?


Thanks,


Steve

 


 


 


 


 


 



Steven, 
  
 Could you send us the target shape file you are using? We will do some research on it. From the exception trace, I doubt that the shape file contains some empty records which we do not support yet.  
  
 If you want, send us the shape file via email support@thinkgeo.com and asked to forward to Yale and it would be nice if you can past the post link to it. 
  
 Thanks. 
  
 Yale 


Steven,


Thanks for your post and data.
 
I think now I have understand your problem. I tried the shape file data you sent and found that the data is a projected data. When we import those data into SQL Server, we have to know and set the correct SRID for it. Otherwise, it cannot work well.
 
 
 
I have sent our GIS specialist an email for help to identify the SRID for the prj string, hope we can get it. Please respond back to keep this thread alive.
 
Thanks.
 
Yale

Hi Yale, 



Thanks for your response.  You will not find an SRID for the data I sent you earlier.  It is a custom projected coordinate system we developed for a client.  If we use the Map Suite product we will have to continue offering support for it.  My original question was "Can we use this coordinate system with Map Suite and SQL Server 2008?"



I note that Map Suite does not know what projection shape files are in, you have to know in advance and project the data according to your needs.  I don't believe there is any metadata in SQL Server 2008 for Projected coordinate systems, only Geodetic.  So I reasoned maybe things worked the same way with SQL Server 2008. 



Hope I'm making sense. 



Thanks, 



Steve



Steven,


Thanks for your feedback and explanations. I think I now understand where the problem now.
 
I have received the reply from our gis specialist and he agree with you that there is no corresponding srid number for this specified projection string, I think this would make things a bit complicated.
 
From MapSuite component, we can support this customized projection string as good as the srid is given, while the SQL Server 2008 only support those predefined SRID projections, it cannot support projection strings instead of srid numbers.
 
So, if we use the Map Suite components with ShapeFile, it can totally support this projection. While when we try to use the MsSqlFeatureLayer, we have to import the data in to SQL server with the right projection set, which is impossible.  The reason is that when we query features inside a boundingbox, the wrong SRID(default is zero as you said) will cause nothing returned.
 
We may probably write our own MsSqlFetureSource and MsSqlFetureLayer in which GetFeturesInsideBoundingBoxCore was rewritten, while I think it will cause some performance loss.
 
Any more questions just feel free to let me know.
 
Thanks.
 
Yale

Yale, 
  
 Thanks for responding.  I guess I don’t understand why a layer from SQL Server 2008 cannot be treated the same way as a shape file.   
  
 What I mean is, apparently when Map Suite opens a shape file it doesn’t know the SRID, it only has a set of coordinates.  And this does not present a problem unless you try to add shape files to the map which are in a different coordinate system.   
  
 Why can’t a layer from SQL Server be treated in the same way?  So you open the layer and it should draw fine even if Map Suite does not know what the coordinate system is.  Then you create a ManagedProj4Projection object (using a Proj4 string, not SRID) that tells Map Suite how to draw if other layers in a different coordinate system are added to the map. 
  
 I guess that’s wishful thinking on my part. 
  
 You mentioned the possibility of implementing MsSqlFeatureLayer and MsSqlFeatureSource that will solve this problem.  Will this happen in the near future? 
  
 Thanks very much! 
  
 Steve

Steven,


Thanks for your post and feedback, and I am sorry for my bad explanations.
 
OK, the key part is hidden in the implementations of GetFeaturesInsideBoudingBoxCore of ShapeFileFeatureSource and MsSqlFeatureSource. This API is very critical for drawing and querying or etc operations.
 
For ShapeFile , as you said and know, we can opens and draws or even queries on it even we do not exactly know its projection coordinate system, that is because we build its indexes with the right coordinate system, each feature bounding Box is calculated correctly and saved into the rtree file correctly. When we querying it using the GetFeaturesInsideBoudingBoxCore, it can return back the correct result as we want.
 
While for SQL server table, it operate in a different way, it uses an SQL statement with SRID involved to query out those features when calling the GetFeaturesInsideBoudingBoxCore, if we store a wrong SRID number in the SQL server table, nothing will be return back. That cause the difference with Shapefile. This can be observed with the use of ExcutingSqlStatement event.

 

((MsSql2008FeatureSource)(sql2008Layer.FeatureSource)).ExecutingSqlStatement += new EventHandler<ExecutingSqlStatementMsSql2008FeatureSourceEventArgs>(FindTheFeatureAUserClickedOn_ExecutingSqlStatement);
void FindTheFeatureAUserClickedOn_ExecutingSqlStatement(object sender, ExecutingSqlStatementMsSql2008FeatureSourceEventArgs e)
{
    if (e.ExecutingSqlStatementType == ExecutingSqlStatementType.GetFeaturesInsideBoundingBoxEx)
    {
        string str = e.SqlStatement;
    }
}


So, we only can get all the features out when calling GetAllFetures. My solution is try to create a customized MsSqlFeatureSource and use, following is the prototype with it without any test, just take a try if you want.
 

public class CustomizedMsSqlFeatureSource : MsSql2008FeatureSource
{
   public CustomizedMsSqlFeatureSource()
       : this(string.Empty, string.Empty, string.Empty, 4326)
   {
   }

   public CustomizedMsSqlFeatureSource(string connectionString, string tableName, string featureIdColumn)
       : this(connectionString, tableName, featureIdColumn, 4326)
   {
   }


   public CustomizedMsSqlFeatureSource(string connectionString, string tableName, string featureIdColumn, int srid)
       : base(connectionString, tableName, featureIdColumn, srid)
   {
   }

   protected override Collection<Feature> GetFeaturesInsideBoundingBoxCore(RectangleShape boundingBox, System.Collections.Generic.IEnumerable<string> returningColumnNames)
   {
       Collection<Feature> featuresInsideBoundingBox = base.GetFeaturesInsideBoundingBoxCore(boundingBox, returningColumnNames);
            if (featuresInsideBoundingBox.Count == 0)
            {
                Collection<Feature> allFeatures = GetAllFeatures(ReturningColumnsType.AllColumns);

                foreach (Feature feature in allFeatures)
                {
                    if(boundingBox.Intersects(feature.GetBoundingBox())
                    {
                        featuresInsideBoundingBox.Add(feature);
                    }
                }
            }

            return featuresInsideBoundingBox;
        }
}


 
Any more questions just feel free to let me know.
 
Thanks.
 
Yale

Yale, 



Thank-you for the example code.  I have a few questions. 



First, once I have implemented the CustomMsSQLFeatureSource, how do I make it a part of a MsSQL2008FeatureLayer?  Since FeatureSource is a Write Only property there is no way to set it. 



Also you said "My solution is try to create a customized MsSqlFeatureSource and use, following is the prototype with it without any test, just take a try if you want."   When you say without any test are you talking about the if statement as below? 


   if (e.ExecutingSqlStatementType == ExecutingSqlStatementType.GetFeaturesInsideBoundingBoxEx)
    {
        string str = e.SqlStatement;
    }

  

So the event handler would be implemented in the CustomMsSqlServerFeatureSource without checking the ExecutingSqlStatementType?  What would happen instead? 



Please forgive all my questions.  I have the task of determining if we can use Map Suite for some very large projects that will be distributed to hundreds of users.  I have to understand whether or not I can implement custom projected coordinate systems for our users, and I have to understand exactly how it works.  A small sample would be really helpful. 



Thanks! 



Steve



Steven, 
  
   Disregard the sample code provided by Yale.  I don’t think this is a good solution for you.  We are going to re-create this issue here in house and give you a solution quickly. 
  
 David

Steven, 
  
   We were able to remedy the issue by setting the layers SRID property to 0.  Just to recap everything… 
  
 1. When you import your data use planar geometry and set the SRID = 0 
 2. When you use our MsSql2008FeatureLayer you need to set the .Srid property to 0. 
 3. You will not need any of that custom code the Yal suggested. 
  
 The hangup was that the Srid property defaults to 4326, the decimal degree number.  We will look to possibly change this default and add the Srid to the constructor set if possible to tip people off they may need to change it. 
  
 Yale was not so familiar with SQL 2008 and got outside his comfort zone.  I saw the post and agreed that we shouldn’t care about the projection.  SQL does care a bit but once you use SRID 0 your are on your own as far as SQL is concerned and it will not try and re-project anything.  Sorry for any delays this has caused you.  Let us know if you have any other issues or questions. 
  
 On a side note if you are interested in creating your own FeatureSources, Layer etc we have some good videos on the subject.  The links are below.  Another good one is on creating custom styles. 
  
 1. Extending Map Suite: Integrating Custom Data Formats 
 2. Extending Map Suite: Exploring Layers 
 3. Extending Map Suite: Creating Custom Styles 
  
 gis.thinkgeo.com/Products/GISComponentsforNETDevelopers/MapSuiteDesktopEdition/Videos/tabid/679/Default.aspx 
  
 David

David, Yale, 



My thanks to both of you for working with me on this issue.    This is a most excellent forum.



I probably didn't make it clear enough in my original post.  I was already successfully displaying a layer from SQL Server 2008 by importing it as planar geometry with a SRID of 0 and then using 0 as the SRID when opening the layer.  My problem is that I must then be able to re-project that layer into a different coordinate system.  When I try to do that I get the error and stack trace I posted earlier.  The main error message is GeometryCollection is not supported now.  My original post explains that I successfully reprojected a shapefile that uses the custom coordinate system, but the same technique does not work with a MsSQL2008FeatureLayer. 



The shapefile I tried with uses the same custom projected coordinate system and is the one I imported into SQL Server 2008 with SRID=0.


Thanks!


Steve


 


 



Steven,


Thanks for your post and feedback. I am ashamed for the misunderstanding and mistakes.
 
Could you send us the data you are trying? I think the problem is probably hidden there. And which projection are you going to project to from the customized projection?
 
I did the following test to simulate the process you are going on and try to recreate the problem you are encountering, unfortunately, I am without good luck. Also, if you are interested, following the following steps
1) Get the data “Austinstreets.shp” attached in our package, its data is in WGS84 coordinate system.
 
2) Use the following code snippet to convert it into the customized projection you mentioned.


string str1 = ManagedProj4Projection.GetEpsgParameters(4326);
string str2 = "+proj=lcc +lat_1=45 +lat_2=49 +lat_0=44.25 +lon_0=-109.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs";
string sourceShapeFile = @"..\Austinstreets.shp";
string targetSourceShapeFile = @"..\Projected\Austinstreets.shp";

ManagedProj4Projection proj4 = new ManagedProj4Projection(str1, str2);
ShapeFileFeatureLayer.SaveToProjection(sourceShapeFile, targetSourceShapeFile, proj4, OverwriteMode.Overwrite);

 
3) Import the projected Austinstreets.shp file into SQL server2008 with Planner Geometry and SRID=0 set.
4) Try the following code without any projection set to see if it works.


private void DisplayMap_Load(object sender, EventArgs e)
        {
            winformsMap1.MapUnit = GeographyUnit.Meter;

            winformsMap1.CurrentExtent = new RectangleShape(-126.4, 48.8, -67.0, 19.0);
            winformsMap1.BackgroundOverlay.BackgroundBrush = new GeoSolidBrush(GeoColor.GeographicColors.ShallowOcean);

            string connectString = @"Data Source=DBSERVERSQL2008\TGSQLSERVER;Initial Catalog=Post7869;User ID=sa;Password=*********";
            MsSql2008FeatureLayer sql2008Layer = new MsSql2008FeatureLayer(connectString, "Austinstreets", "Id");
            sql2008Layer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = AreaStyles.Country1;
            sql2008Layer.ZoomLevelSet.ZoomLevel01.DefaultLineStyle = LineStyles.LocalRoad1;
            sql2008Layer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
            sql2008Layer.Srid = 0;

            sql2008Layer.Open();
            Collection<Feature> allFeatures = sql2008Layer.QueryTools.GetAllFeatures(ReturningColumnsType.AllColumns);
            sql2008Layer.Close();

            LayerOverlay staticOverlay = new LayerOverlay();
            staticOverlay.Layers.Add("Sql2008Layer", sql2008Layer);
            winformsMap1.Overlays.Add(staticOverlay);

            sql2008Layer.Open();
            winformsMap1.CurrentExtent = sql2008Layer.GetBoundingBox();
            
            winformsMap1.Refresh();
        }

 
5) Try the following code with any projection set to see if it works, here I project the data back again into WGS84 coordinate system, maybe in your case it is different.:).


private void DisplayMap_Load(object sender, EventArgs e)
        {
            string str1 = ManagedProj4Projection.GetEpsgParameters(4326);
            string str2 = "+proj=lcc +lat_1=45 +lat_2=49 +lat_0=44.25 +lon_0=-109.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs";
            ManagedProj4Projection proj4 = new ManagedProj4Projection(str2, str1);

            winformsMap1.MapUnit = GeographyUnit.DecimalDegree;

            winformsMap1.CurrentExtent = new RectangleShape(-126.4, 48.8, -67.0, 19.0);
            winformsMap1.BackgroundOverlay.BackgroundBrush = new GeoSolidBrush(GeoColor.GeographicColors.ShallowOcean);

            string connectString = @"Data Source=DBSERVERSQL2008\TGSQLSERVER;Initial Catalog=Post7869;User ID=sa;Password=******";
            MsSql2008FeatureLayer sql2008Layer = new MsSql2008FeatureLayer(connectString, "Austinstreets", "Id");
            sql2008Layer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = AreaStyles.Country1;
            sql2008Layer.ZoomLevelSet.ZoomLevel01.DefaultLineStyle = LineStyles.LocalRoad1;
            sql2008Layer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
            sql2008Layer.Srid = 0;
            sql2008Layer.FeatureSource.Projection = proj4;

            sql2008Layer.Open();
            Collection<Feature> allFeatures = sql2008Layer.QueryTools.GetAllFeatures(ReturningColumnsType.AllColumns);
            sql2008Layer.Close();

            LayerOverlay staticOverlay = new LayerOverlay();
            staticOverlay.Layers.Add("Sql2008Layer", sql2008Layer);
            winformsMap1.Overlays.Add(staticOverlay);

            sql2008Layer.Open();
            winformsMap1.CurrentExtent = sql2008Layer.GetBoundingBox();
            
            winformsMap1.Refresh();
        }



 
Any more questions please feel free to let me know.
 
Thanks.
 
Yale

Yale,


Success!



Thank you very much for your efforts.  I tried the example you gave with the Austinstreets shapefile and it worked just fine!  So then I tried importing several of my own shapefiles into SQL Server 2008 (that are also in the custom coordinate system) and they worked fine also.  So there is obviously a problem with the shapefile I was originally using.  Do you have any idea what kind of problem would cause the error message I'm getting,  GeometryCollection is not supported now?


I actually sent you the data earlier, but I can send it again if you like.  However, I would think fixing my bad data is a little outside the scope of this forum.



I want to thank you again for sticking with this.  I have participated in many support forums and this one is the best I've ever seen. 



Best regards, 



Steve



Steven, 
  
 Thanks for your post and feedback. 
  
 Just let you know that I am fully working on the support of GeometryCollection now, hopefully we can get it supported in the coming one day or two. And if you want, just send me the shape file or tell me the name of the shape files, I will try to find it from tons of files. 
  
 Thanks. 
  
 Yale 


Yale, 
  
 Thanks for you reply.  Your remark about support for the GeometryCollection clued me in to what’s actually going on with my data.  I originally thought that the error message I was getting was not the actual problem.  My original shapefile was a polygon layer, and shapefiles do not support multiple shapetypes.  Well, it supports two, Polygon and Multipolygon.  My assumption is that a layer that supports GeometryCollection would support points, lines and polygons.   
  
 I did a query on my problem layer:  
 
select * from STAND 
where shape.STGeometryType() <> 'Polygon’
and shape.STGeometryType() <> ‘MultiPolygon’
 
 Out of 10,196 rows, ONE of them was reported to be a GeometryCollection.  Taking a look at the shape, it had a long spike coming out of it that must have been interpreted as a line.  I edit the shapefile, removing the vertex creating the spike, then re-imported the data and now the layer works fine. 
  
 Yale, thanks again for all you help! 
  
 Steve

Steven,


Thanks for your feedback and letting me know the status.
 
Following site gave some information and samples about the well-known text, unfortunately, we could not have chance to support the GeometryCollection type up to now. While, in the next coming versions, I guess the GeometryCollection will be supported.
en.wikipedia.org/wiki/Well-known_text
 
As you already know, one standard shape file should be only one type, while for some reason like editing or from some special tools or softwares; the shape file may be not so standard. Besides, the SQL server and Oracle support GeometryCollection, so we decide to make it supported.
 
Any all, thanks for your letting me know your status.
 
Thanks.
 
Yale

Steven, 
  
   I’m glad you were able to find the culprit feature.  In any case we have the GeometryCollection support almost done now.  We have needed to have that for awhile anyway, thanks for giving us the nudge we needed. :-) 
  
 David