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;
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.