Monday, January 3, 2011

Working with CAD files in ArcObjects

 

Cad files are files used by architects to draw buildings sketches. The files are with the extension of dwg.

As always with ESRI I started by copying their examples and trying them for myself. Taken from:

http://help.arcgis.com/en/sdk/10.0/arcobjects_net/conceptualhelp/index.html#/How_to_return_CAD_drawing_layer_properties/0001000003mp000000/

http://help.arcgis.com/en/sdk/10.0/arcobjects_net/conceptualhelp/index.html#/How_to_access_members_that_control_the_CAD_drawing_dataset/000100000m01000000/

I extracted the code to create IWorkspace to my WorkspaceProvider:

        public CadWorkspaceUtils GetCadWorkspaceUtils(string path)

        {

            var workspaceFactory = new CadWorkspaceFactory();

            var workspace = (IFeatureWorkspace)workspaceFactory.OpenFromFile(path, 0);

            return new CadWorkspaceUtils(workspace);

        }

 

I have created a new class – CadWorkspaceUtils that inherits from WorkspaceUtils

 with a function to get back the geometries in the file:

        public List<IGeometry> GetGeometries(string fileName, GeometryType type)

        {

            var featureClassName = GetFeatureClassName(fileName, type);

 

            var result = new List<IGeometry>();

 

            DoActionOnSelectFeatures(featureClassName, "",

                feature =>

                {

                    var shape = feature.Shape.Copy();

                    shape.SpatialReference = GeometryUtils.CreateWgsSpatialReference();

                    result.Add(shape);

                });

 

            return result;

        }

Helper method:

        private string GetFeatureClassName(string fileName, GeometryType type)

        {

 

            if (type != GeometryType.Polyline)

                throw new NotImplementedException("The method is only implemented for polyline geometries");

 

            return String.Concat(fileName, ":Polyline");

        }

 

Thank god I only need polyline type because I haven't seen an example with anything different.

 

 

// TODO: Make links user friendly

//TODO: Post about WorkspaceUtils first 

Resources:

http://help.arcgis.com/en/sdk/10.0/arcobjects_net/conceptualhelp/index.html#/CAD_data/000100000mt1000000/

http://help.arcgis.com/en/sdk/10.0/arcobjects_net/componenthelp/index.html#//001900000064000000

http://help.arcgis.com/en/sdk/10.0/arcobjects_net/componenthelp/index.html#/Members/0047000033sn000000/

http://help.arcgis.com/en/arcgisdesktop/10.0/help/index.html#//00120000001z000000.htm

 

 

Multithreading with ArcObjects

 

Multithreading should be discouraged with ESRI code simply because of the usage of COM classes, typically you will get COM exceptions saying that the RCW is no longer connected to the COM class.

But if you still want to implement multithreading within your application then you got to right place.

Things that must be considered:

1.       Licensing – calling:

RuntimeManager.Bind(ProductCode.Server)

If you call it twice you might get an exception that you don't have any free license (that will happen in both single threaded and multithreaded applications).

2.        IWorkspace – you can't use the workspace you initialized in a different thread – COM exception (RCW).

3.       All singleton objects created in your application might not work if they use a saved ESRI object (for example I have a WorkspaceProvider factory class that caches IWorkspace by their connection string for reuse).

4.       STA (single threaded apartment) vs MTA (multithreaded apartment) – you can mark a static method as either [STAThread] or [MTAThread], and for each the threading apartment used is different. ArcObjects use the STA the .NET default is (of course) MTA meaning any time you want to use a static method you should use it with the STA attribute.

5.       Singleton classes that save inner ArcObjects variables (as opposed to providing methods like a utils class) should also save the thread id and when the current thread id is different reload the variables. Otherwise prepare yourself and catch the incoming COM exception (RCW).

The simplest way of doing multithreading with ArcObjects is not doing it at all. Nice – but how? Implement a web/WCF service that talks with ArcObjects and within your multithreaded application call that service. An important note though is that a web/WCF service needs ArcGIS Server license (see Upgrading the code base from ArcGIS 9.3.1 to 10).

You might think this is simple until you realize that WCF sitting on IIS is multithreaded by design.

The simplest solution is to always use newly created objects. In this case though the simplest way is also the slowest since sometimes the IWorkspace can be used for several feature layers/tables and recreating this object takes time.

The code I use has a singleton factory class that handles the creation of IWorkspace classes. The only fix you need to add is mark the instance of that factory with the ThreadStaticAttribute attribute.

//TODO: add example

//TODO: continue this post

//TODO: the font formatting is off

 

 

References:

Writing multithreaded ArcObjects code

http://gis.stackexchange.com/questions/2490/accessing-arcgis-layers-from-seperate-thread

http://gis.stackexchange.com/questions/2688/possible-cause-of-accessviolation-in-arcobject-multithreaded-app

http://funcode.org/article/csharp/creating-per-thread-static-fields.aspx

 

ESRI License error

 

image

Text version:

Initialization Error

The runtime application type must be specified before license initialization.

 

The annoying thing is that I initialized my license – with Engine license and the application run through IGeometry, IClone and the like. But when I got to this line:

result = ((ITopologicalOperator) result).Union(geometry);

I got the pop up.

Does Union need a different license?

From the ESRI site:

image

 

del.icio.us Tags: ,,,
IceRocket Tags: ,,,

Upgrading the code base from ArcGIS 9.3.1 to 10

I found the tool (VS2008->Tools-> ArcGIS Code Migration analyzer):

image

Not very helpful, why?

Because the tool assumed that I keep my reference Dlls in Program files\ArcGis\...

And I don't!

In my organization there are non GIS programmers and they don't have ArcEngine installed, so we keep the ESRI Dlls in a Libraries sub folder in the TFS.

That way the build server can build the project but not necessarily run it.

So the tool gave me the message of:

ArcGIS Code Migration analyzer run on 29/11/2010 at 14:22

--------------------------------------------------------------

Analyzing project: Company.GIS.Core.Esri.ConfigTests

No ESRI assembly references found. Project skipped for conversion.

-------------------------

So I had to manually change the Dlls (but that is ok since I needed to add them to the TFS).

Everything was build without errors but when testing all of the ESRI tests failed with:

System.Runtime.InteropServices.COMException: Retrieving the COM class factory for component with CLSID {D9B4FA40-D6D9-11D1-AA81-00C04FA33A15} failed due to the following error: 80040111..

It failed on this line:

             IWorkspaceFactory2  workspaceFactory = new  SdeWorkspaceFactoryClass ();



I did a bit of Googling and found this post:


http://blogs.esri.com/Dev/blogs/arcgisdesktop/archive/2009/12/01/Migrating-to-Engine-9.4-and-using-the-VersionManager.aspx


So I have added the ESRI.ArcGIS.Version.dll to the libraries folder and the reference and added this code:

     public  class  EsriInitilization 
     {
         private  static  bool  _isStarted = false ;
 
         public  static  void  Start()
         {
             if  (!_isStarted)
             {
                 if  (!RuntimeManager .Bind(ProductCode .Server))
                 {
                     if  (!RuntimeManager .Bind(ProductCode .Engine))
                     {
                         throw  new  ApplicationException (
                             "Unable to bind to ArcGIS license Server nor to Engine. " + 
                             "Please check your licenses." );
                     }
                 }
                 _isStarted = true ;
             }
         }
     }
 

What this does is tries to Bind the server license and if it fails it tries the engine license, it can be found here on the examples (for some reason ESRI didn't include what the return value of the Bind method in their help but are using it in their example… (I tried sending them an email asking for a fix to that but their mail address doesn't work…))


I later found out that the _isStarted is needed when you use Engine license since it seems calling the method more than once will go through both ifs and throw the ApplicationException exception (because the license is already taken).


And then I tested again.
This time I got a pop up:
image 
The text:

VSTestHost.exe - Entry Point Not Found
The procedure entry point ?doctypeWhitespace@SAXParser@xercesc_2_7@@UAEXQB_WI@Z could not be located in the dynamic link library xerces-c_2_7.dll.

And for all the randomness in the files and text they are the same in three runs…
So debugging, and we did pass one checkpoint in our road, we now get the error in this line:

                          [Code for initializing the license]

             return  workspaceFactory.OpenFromString (sdeConnectionString, 0);

The xerces-c_2_7.dll is "designed to provide a way for a process to call a function that is not part of its executable code." (at least according to this).

So Googled again and found this forum.

The answer this time was uninstalling ArcSde 9.3.1 (I installed 10 because some of my tests are of deployment).

Why doesn't ESRI's Detect Conflict tool didn't uninstall this as well???

Some tests still didn't pass:

1. Tests that check the description of a ComException (it might have changed but not by much…) – I fixed those by hand.
2. One test that renames a layer that has polyline data. Right now it's in a piece of code we don't really use (for easier DB deployment). The really messed up thing is that it works with polygon and point data…

The error that I got was:

System.Runtime.InteropServices.COMException: The number of points is less than required for feature.

(It's has a polyline with 4 points, and the polyline is saved and read from the DB just fine).

I later fixed the problem I will post in Rename Polyline layer

And then I changed the DB to a DB running SDE 10.

All was well until I tried to generate my base classes using MyGeneration code generation tool. It seems that SDE changed the DateTime type that they use to datetime2 and MyGeneration didn't know that type.
I fixed that quit easily.

Another change in the DB is that SDE tables (without a shape) no longer have OBJECTID.

Be Warned if you use that field as an identification column!

Build Warning after the changes:

  • Using the reference ESRI.ArcGIS.ADF like in ComReleaser is obsolete use instead ESRI.ArcGIS.ADF.Connection.Local – only needed to change the reference

When testing the application with only ArcEngine License we found out that you need ArcGis Server license in order to use ArcObjects within a web service / WCF service. Be warned!!!

 

Hello Note

Hi all,
This is not really my blog but just a place holder to the notes of my real blog:
Most of the post on this site will be a bit raw and may be updated from time to time
I will add a link from each "draft" post to the real post when they are done.
Please give me feedback even if it's just "you have a typo there".
 
I am writing this because I believe it can help people even if it is a bit raw.
And if it help even one person I did my job right!

See you later,
Roy Dallal

del.icio.us Tags: ,,
IceRocket Tags: ,,