Translating between AutoCAD drawing points and geographic locations using .NET – Part 1

I received a question from Coralie Jacobi, recently, in response to this recent post:

Saw your post a while ago on the geolocation in 2015. This functionality is something that we will use a great deal and I will definitely be writing some code to utilize it. What I'd like to see in your blog is something that would show me how to get to the lat\long values of the location I have picked and convert them to the coordinate system that I have selected instead of having the user have to stipulate the location in AutoCAD.

In this post that's just what we're going to take a look at: using the GeoLocation object attached to a drawing to translate between drawing points and lat-long values (and vice-versa). The capability is there โ€“ assuming a drawing has been geo-located โ€“ and is very straightforward to use. We'll create a simple utility function to check the existence of a geo-location object and another to do the actual translation. The translation itself is extremely easy to perform: it's a simple matter of choosing which of two methods to call (i.e. in which direction to do the translation).

The translation is based on the currently used coordinate system, which you can also via the GeoLocation object. In fact in tomorrow's post we're going to see just that: how we can retrieve information about the current coordinate system (in our case we're going to focus on the name, but the code will be easy to adapt to get other information).

Let's start with a quick Screencast of the code in action:

Here's the C# code that implements the new helper functions and two commands that use them: LLFP and PFLL (for LatLongFromPoint and PointFromLatLong, respectively). We might also have exposed the underlying translation helper to LISP, which would make it easier to call from the command-line, but that's been left as an exercise for the reader. The implementation includes the previous commands to add a geographic location to the current drawing as well as to create an embedded image of the current geography.

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

 

namespace GeoLocationAPI

{

  public class Commands

  {

    [CommandMethod("IGR")]

    public void InsertGeoRef()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

      var db = doc.Database;

      var msId = SymbolUtilityServices.GetBlockModelSpaceId(db);

 

      if (HasGeoData(db))

      {

        // Report and return: could also open the object for

        // write and modify its properties, of course

 

        ed.WriteMessage("\nDrawing already has geo-location data!");

        return;

      }

 

      // Let's create some geolocation data for this drawing,

      // using a handy method to add it to the modelspace

      // (it gets added to the extension dictionary)

 

      var data = new GeoLocationData();

      data.BlockTableRecordId = msId;

      data.PostToDb();

 

      // We're going to define our geolocation in terms of

      // latitude/longitude using the Mercator projection

      // http://en.wikipedia.org/wiki/Mercator_projection

 

      data.CoordinateSystem = "WORLD-MERCATOR";

      data.TypeOfCoordinates = TypeOfCoordinates.CoordinateTypeGrid;

 

      // Use the lat-long for La Tene, my local "beach"

      // (it's on a lake, after all :-)     

 

      var geoPt = new Point3d(7.019438, 47.005247, 0);

 

      // Transform from a geographic to a modelspace point

      // and add the information to our
geolocation data

 

      var wcsPt = data.TransformFromLonLatAlt(geoPt);

      data.DesignPoint = wcsPt;

      data.ReferencePoint = geoPt;

 

      // Let's launch the GEOMAP command to show our geographic

      // overlay

 

      ed.Command("_.GEOMAP", "_AERIAL");

 

      // Now we'll add a circle around our location

      // and that will provide the extents for our zoom

 

      using (var tr = db.TransactionManager.StartTransaction())

      {

        var ms =

          tr.GetObject(msId, OpenMode.ForWrite) as BlockTableRecord;

        if (ms != null)

        {

          // Add a red circle of 7K units radius

          // centred on our point

 

          var circle = new Circle(wcsPt, Vector3d.ZAxis, 7000);

          circle.ColorIndex = 1;

          ms.AppendEntity(circle);

          tr.AddNewlyCreatedDBObject(circle, true);

        }

        tr.Commit();

      }

 

      // And we'll zoom to the circle's extents

 

      ed.Command("_.ZOOM", "_OBJECT", "_L", "");

    }

 

    [CommandMethod("CGI")]

    public void CreateGeoMapImage()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

      var db = doc.Database;

 

      // Get the first corner of our area to convert to a

      // GeomapImage

 

      var ppo = new PromptPointOptions("\nSpecify first corner");

      var ppr = ed.GetPoint(ppo);

      if (ppr.Status != PromptStatus.OK)

        return;

 

      var first = ppr.Value;

 

      // And get the second point as a corner (to rubber-band

      // the selection)

 

      var pco =

        new PromptCornerOptions("\nSpecify second corner", first);

      ppr = ed.GetCorner(pco);

 

      if (ppr.Status != PromptStatus.OK)

        return;

 

      var second = ppr.Value;

 

      // We'll use an event handler on the Database to check for

      // GeomapImage entities being added

      // (we'll use a lambda but assigned to a variable to be

      // able to remove it, afterwards)

 

      ObjectId giId = ObjectId.Null;

   &#
160;  ObjectEventHandler handler =

        (s, e) =>

        {

          if (e.DBObject is GeomapImage)

          {

            giId = e.DBObject.ObjectId;

          }

        };

 

      // Simply call the GEOMAPIMAGE command with the two points

 

      db.ObjectAppended += handler;

      ed.Command("_.GEOMAPIMAGE", first, second);

      db.ObjectAppended -= handler;

 

      // Only continue if we've collected a valid ObjectId

 

      if (giId == ObjectId.Null)

        return;

 

      // Open the entity and change some values

 

      try

      {

        using (var tr = doc.TransactionManager.StartTransaction())

        {

          // Get each object and check if it's a GeomapImage

 

          var gi =

            tr.GetObject(giId, OpenMode.ForWrite) as GeomapImage;

          if (gi != null)

          {

            // Let's adjust the brightmess/contrast/fade of the

            // GeomapImage

 

            gi.Brightness = 90;

            gi.Contrast = 40;

            gi.Fade = 20;

 

            // And make sure it's at the right resolution and

            // shows both aerial and road information

 

            gi.Resolution = GeomapResolution.Optimal;

            gi.MapType = GeomapType.Hybrid;

 

            gi.UpdateMapImage(true);

          }

 

          tr.Commit();

        }

      }

      catch (Autodesk.AutoCAD.Runtime.Exception)

      {

        ed.WriteMessage(

          "\nUnable to update geomap image entity." +

          "\nPlease check your internet connectivity and call " +

          "GEOMAPIMAGEUPDATE."

        );

      }

    }

 

    [CommandMethod("LLFP")]

    public void LatLongFromPoint()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

      var db = doc.Database;

 

      if (!HasGeoData(db))

      {

        ed.WriteMessage(

          "\nCurrent drawing has no geo-location information."

        );

        return;

      }

 

      // Get the drawing point to be translated into a lat-lon

 

      var ppo = new PromptPointOptions("\nSpecify point");

      var ppr = ed.GetPoint(ppo);

      if (ppr.Status != PromptStatus.OK)

        return;

 

      var dwgPt = ppr.Value;

 

      // Translate the drawing point to a lat-lon

 

      var lonlat = TranslateGeoPoint(db, dwgPt, true);

 

      ed.WriteMessage(

        "\nLatitude-longitude is {0},{1}", lonlat.Y, lonlat.X

      );

    }

 

    [CommandMethod("PFLL")]

    public void PointFromLatLong()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

      var db = doc.Database;

 

      if (!HasGeoData(db))

      {

        ed.WriteMessage(

          "\nCurrent drawing has no geo-location information."

        );

        return;

      }

 

      // Get the latitude and longitude to be translated

      // to a drawing point

 

      var pdo = new PromptDoubleOptions("\nEnter latitude");

      var pdr = ed.GetDouble(pdo);

      if (pdr.Status != PromptStatus.OK)

        return;

 

      var lat = pdr.Value;

 

      pdo.Message = "\nEnter longitude";

      pdr = ed.GetDouble(pdo);

      if (pdr.Status != PromptStatus.OK)

        return;

 

      var lon = pdr.Value;

 

      var lonlat = new Point3d(lon, lat, 0.0);

 

      // Translate the lat-lon to a drawing point

 

      var dwgPt = TranslateGeoPoint(db, lonlat, false);

 

      ed.WriteMessage(

        "\nDrawing point is {0},{1},{2}", dwgPt.X, dwgPt.Y, dwgPt.Z

      );

    }

 

    private Point3d TranslateGeoPoint(

      Database db, Point3d inPt, bool fromDwg

    )

    {

      using (

        var tr = db.TransactionManager.StartOpenCloseTransaction()

      )

      {

        // Get the drawing's GeoLocation object

 

        var gd =

          tr.GetObject(db.GeoDataObject, OpenMode.ForRead)

            as GeoLocationData;

 

        // Get the output point...

        // dwg2lonlat if fromDwg is true,

        // lonlat2dwg otherwise

 

        var outPt =

          (fromDwg ?

            gd.TransformToLonLatAlt(inPt) :

            gd.TransformFromLonLatAlt(inPt)

          );

        tr.Commit();

 

        return outPt;

      }

    }

 

    private static bool HasGeoData(Database db)

    {

      // Check whether the drawing already has geolocation data

 

      bool hasGeoData = false;

      try

      {

        var gdId = db.GeoDataObject;

        hasGeoData = true;

      }

      catch { }

      return hasGeoData;

    }

  }

}

In the next post we'll build on this code to get more information about the chosen coordinate system.

6 responses to “Translating between AutoCAD drawing points and geographic locations using .NET – Part 1”

  1. Would you be able to cover getting the geodetic distance along with forward and reverse azimuths just like the MAPDIST command does in Map 3D; thanks Kean, Dawson

    1. I was with you until the word "geodetic". ๐Ÿ™‚

      I'll have to do some research to even understand the question, Dawson!

      Kean

    2. OK, I think I now understand the question. Looks like it's unfortunately beyond the scope of this blog (i.e. I have neither the time nor the knowledge to attempt this ๐Ÿ™ :-).

      Kean

  2. my project is to extract the database from a step file using windows form and C #.
    my problem is that I do not know how to extract the geometric coordinates of each vertex of the piece and its location on the piece.
    cordially

  3. Is there a way to load this lisp into bricscad? It now gives an error to me. I do have the Pro version of Brics if that matters. Autocad is just to expensive for what I use it for.

    1. Kean Walmsley Avatar

      I suggest asking people at BricsCAD about this.

      Kean

Leave a Reply to oumaima aatef Cancel reply

Your email address will not be published. Required fields are marked *