Several developers have asked how to determine the azimuth of an element, i.e. the angle between the element and true north. This calculation is obviously strongly affected by the project location.
Determining the angle of an element is pretty simple. All Revit elements have a Location property. For some, the location is a point, while others can be line or curve based, in which case their location is an instance of the LocationCurve class. One of the properties of the LocationCurve is Curve, which returns the actual geometry curve, which may be one of the derived classes Arc, Line, Ellipse or NurbSpline. In addition to providing access to the underlying geometry curve, the location curve class also provides methods to move and rotate the element whose location it defines, and to determine or modify the order of the neighbouring elements joining to the end of the element's location curve.
In order to determine the angle of an element, you can convert the two endpoints of its location curve to a vector and then measure the angle of that vector relative to some other vector, for instance the X axis or the north direction.
Revit stores true north in the ProjectPosition object stored in the ProjectLocation, accessible through the ProjectLocations property on the document object. You can query the ProjectPosition.Angle property, which returns the angle difference between the project north and true north measured in radians. It can have a value from -π to +π.
To determine the element azimuth, you can determine the element angle and the angle between project north and true north, and then simply add the two.Some notes on the Revit geometry angle methods: The Angle() method of the XYZ class treats its XYZ instances as vectors, not points. This is what the Revit API help has to say about this:
The XYZ.Angle() method returns the angle between this vector and the specified vector.
Return Value: The real number between 0 and π equal to the angle between the two vectors.
Remarks: The angle between the two vectors is measured in the plane spanned by them.
If you are interested in angles around the full circle, i.e. in the interval [0, 2π], you should use AngleTo() instead of Angle(), which returns a value in the interval [0, π].
Here is a code example measuring various kinds of element angles and printing the project north deviation:
class CmdAzimuth : IExternalCommand { Element SelectSingleElement( Document doc ) { Selection sel = doc.Selection; Element e = null; sel.Elements.Clear(); sel.StatusbarTip = "Please select a line"; if( sel.PickOne() ) { ElementSetIterator elemSetItr = sel.Elements.ForwardIterator(); elemSetItr.MoveNext(); e = elemSetItr.Current as Element; } return e; } public CmdResult Execute( ExternalCommandData commandData, ref String message, ElementSet elements ) { Application app = commandData.Application; Document doc = app.ActiveDocument; Element e = SelectSingleElement( doc ); if( null == e ) { message = "No element selected"; return CmdResult.Failed; } LocationCurve curve = e.Location as LocationCurve; if( null == curve ) { message = "No curve available"; return CmdResult.Failed; } XYZ p = curve.Curve.get_EndPoint( 0 ); XYZ q = curve.Curve.get_EndPoint( 1 ); Debug.WriteLine( "Start point " + Util.PointString( p ) ); Debug.WriteLine( "End point " + Util.PointString( q ) ); double a = p.Angle( q ); Debug.WriteLine( "Angle between start and end points = " + Util.AngleString( a ) ); XYZ v = q - p; a = XYZ.BasisX.Angle( v ); Debug.WriteLine( "Angle between points measured from X axis = " + Util.AngleString( a ) ); XYZ z = XYZ.BasisZ; a = XYZ.BasisX.AngleAround( v, z ); Debug.WriteLine( "Angle around measured from X axis = " + Util.AngleString( a ) ); foreach( ProjectLocation location in doc.ProjectLocations ) { ProjectPosition projectPosition = location.get_ProjectPosition( XYZ.Zero ); double pna = projectPosition.Angle; Debug.WriteLine( "Angle between project north and true north " + Util.AngleString( pna ) ); } return CmdResult.Succeeded; } }
Running this command in a project and selecting a model line with an angle of 35 degrees to the X axis generates the following result in the Visual Studio debug output window:
Start point (-43.84,31.27,0) End point (-28.79,41.81,0) Angle between start and end points = 19.95 degrees Angle between points measured from X axis = 35 degrees Angle around measured from X axis = 35 degrees Angle between project north and true north 0 degrees
You might also have a look at the Revit SDK SharedCoordinateSystem sample to see another example of using the ProjectPosition object.
I am adding the complete Visual Studio solution here. This version 1.0.0.7 includes all commands discussed so far: CmdListWalls, CmdRelationshipInverter, CmdWallDimensions, CmdFilterPerformance, CmdGetMaterials and the new CmdAzimuth.
Which vector corresponds to north? Is it (0, 1, 0)?
According to a picture in the "Revit 2010 API Developer Guide", the y-Axis represents North/South and the x-Axis represents East/West - alas, it is not quite clear which way...
Posted by: Daren Thomas | September 16, 2009 at 08:46
Dear Daren,
Yes, (0,1,0) is probably right. You can also use the XYZ.BasisY property instead of creating your own new XYZ instance for it.
Cheers, Jeremy.
Posted by: Jeremy Tammik | September 22, 2009 at 01:26
Hi Jeremy,
Thank you for very useful blog. I create my application with VB VSTA borrowing ideas how to manage Revit objects from your site. One of the lines of your code doesn't work for me.
XYZ v = q - p;
gives en error:
"Operator '-' is not defined for types 'Autodesk.Revit.Geometry.XYZ' and 'Autodesk.Revit.Geometry.XYZ'."
What the reason could be for the issue?
Thanks in advance.
EK
Posted by: Evgeny | December 17, 2009 at 13:15
Hm... I found that the error appears when there is a reference to Revit.proxy. But when the RevitAPI is referenced the code is OK. But I can't use the RevitAPI for VSTA, because errors occure in other places of code.
Can you suggest something to get points subtraction?
Thanks,
EK.
Posted by: Evgeny | December 17, 2009 at 13:49
Dear EK,
I know nothing of VSTA, I just use the real API. I suspect that 'new XYZ()' does not work in VSTA; you have to use the Application.NewXYZ method instead of the default constructor.
Cheers, Jeremy.
Posted by: Jeremy Tammik | December 17, 2009 at 18:59
Jeremy,
I was able to resolve the problem, using the Subtract method:
Dim v As XYZ = p2.Subtract(p1)
Sorry for the VB code. I believe you get the idea. This line works fine for me and it looks more reliable then "-" operator, which doesn't work, although it is defined for XYZ object.
Sorry for false alarm and thank you for your great blog.
Regards,
EK
Posted by: Evgeny | December 18, 2009 at 09:59
Dear Evgeny,
Congratulations on solving the issue and thank you for sharing the solution.
So is the final result of your experience that you need to use explicit method calls in VSTA instead of built-in operators, e.g. Application.NewXYZ instead of 'new' for creating and Subtract instead of '-' for subtracting?
Cheers, Jeremy.
Posted by: Jeremy Tammik | December 19, 2009 at 02:47
Hi Jeremy,
I am able to draw the model lines in Revit file using c# code. But the problem ariseing is that when i draw the model line it intersect the walls.
Will u please provide me some tips that How to check the elements collapse with the model lines and change the direction of model line from that position to other side??
Thanks in Advance.
Ritish
Posted by: Ritish | June 30, 2010 at 08:39
Dear Ritish,
One approach that you can use is to determine the intersections of the model line with building elements using the FindReferencesByDirection method, as discussed in a number of posts:
http://thebuildingcoder.typepad.com/blog/2010/06/intersection-between-elements.html
http://thebuildingcoder.typepad.com/blog/2010/06/devcamp-session-on-whats-new.html
http://thebuildingcoder.typepad.com/blog/2010/01/findreferencesbydirection.html
Since I will be going on holiday now, I will not be able to answer any updates for while, so please be patient ... Thank you!
Cheers, Jeremy.
Posted by: Jeremy Tammik | June 30, 2010 at 08:49