COGO: Create point from bearing and distance

Sometimes the definition of an object can be as a bearing and distance from a known point. This often happens in unbiased statistical sampling, or when creating polygons describing land titles from coordinate geometry (COGO) measurements.

Here is a version of the PointFromBearingAndDistance PL/SQL function I wrote for Oracle Spatial many years ago for SQL Server 2008.

Note that I have a schema call cogo in which I create functions like this. You can use anything you like.

 /**
 * @function   : PointFromBearingAndDistance
 * @precis     : Returns a point geometry given a starting point, bearing and distance.
 * @version    : 1.0
 * @usage      : FUNCTION PointFromBearingAndDistance(@p_dStartE   float,
 *                                                     @p_dStartN   float,
 *                                                     @p_dBearing  float,
 *                                                     @p_dDistance float,
 *                                                     @p_precision int = 3,
 *                                                     @p_srid      int = 0 )
 *                 RETURNS GEOMETRY
 *               eg select cogo.PointFromBearingAndDistance(0,0,45,100,3,0).STAsText();
 * @param      : p_dStartE     : X Ordinate of start point of bearing
 * @paramtype  : p_dStartE     : FLOAT
 * @param      : p_dStartN     : Y Ordinate of start point of bearing
 * @paramtype  : p_dStartN     : FLOAT
 * @param      : p_dBearing    : Whole circle bearing (ie 0 North to 360 degree clockwise rotation)
 * @paramtype  : p_dBearing    : FLOAT
 * @param      : p_dDistance   : Distance in projection units from point along bearing
 * @paramtype  : p_dDistance   : FLOAT
 * @param      : p_precision   : Precision of any XY value ie number of significant digits. If null then 3 is assumed (ie 1 mm): 3456.2345245 -> 3456.235 
 * @paramtype  : p_precision   : INTEGER
 * @param      : p_srid        : SRID of output shape
 * @paramtype  : p_srid        : INTEGER
 * @return     : geometry      : Point geometry 
 * @rtnType    : geometry      : GEOMETRY
 * @note       : Does not throw exceptions
 * @note       : Assumes planar projection eg UTM.
 * @history    : Simon Greener  - Feb 2005 - Original coding.
 * @history    : Simon Greener  - May 2011 - Converted to SQL Server
 */
 CREATE FUNCTION 
 [cogo].[PointFromBearingAndDistance] (@p_dStartE   Float,
                                       @p_dStartN   Float,
                                       @p_dBearing  Float,
                                       @p_dDistance Float,
                                       @p_precision int = 3,
                                       @p_srid      int = 0 )
 RETURNS geometry
 AS
 Begin
   Declare
       @dAngle1    Float,
       @dAngle1Rad Float,
       @dDeltaN    Float,
       @dDeltaE    Float,
       @dEndE      Float,
       @dEndN      Float,
       @precision  int = CASE WHEN @p_precision IS NULL THEN  3 ELSE @p_precision END,
       @srid       int = CASE WHEN @p_srid      IS NULL THEN  0 ELSE @p_srid      END;
   BEGIN
       IF ( @p_dStartE   IS NULL OR
            @p_dStartN   IS NULL OR
            @p_dBearing  IS NULL OR
            @p_dDistance IS NULL )
       RETURN NULL;
  
       -- First calculate dDeltaE and dDeltaN
       IF ( @p_dBearing < 90 )
       BEGIN
           SET @dAngle1    = 90 - @p_dBearing;
           SET @dAngle1Rad = @dAngle1 * PI() / 180;
           SET @dDeltaE    = Cos(@dAngle1Rad) * @p_dDistance;
           SET @dDeltaN    = Sin(@dAngle1Rad) * @p_dDistance;
       END
       IF ( @p_dBearing < 180 )
       BEGIN
           SET @dAngle1    = @p_dBearing - 90;
           SET @dAngle1Rad = @dAngle1 * PI() / 180;
           SET @dDeltaE    = Cos(@dAngle1Rad) * @p_dDistance;
           SET @dDeltaN    = Sin(@dAngle1Rad) * @p_dDistance * -1;
       END
       IF ( @p_dBearing < 270 )
       BEGIN
           SET @dAngle1    = 270 - @p_dBearing;
           SET @dAngle1Rad = @dAngle1 * PI() / 180;
           SET @dDeltaE    = Cos(@dAngle1Rad) * @p_dDistance * -1;
           SET @dDeltaN    = Sin(@dAngle1Rad) * @p_dDistance * -1;
       END
       IF ( @p_dBearing <= 360 )
       BEGIN
           SET @dAngle1    = @p_dBearing - 270;
           SET @dAngle1Rad = @dAngle1 * PI() / 180;
           SET @dDeltaE    = Cos(@dAngle1Rad) * @p_dDistance * -1;
           SET @dDeltaN    = Sin(@dAngle1Rad) * @p_dDistance;
       End 
       -- Calculate the easting and northing of the end point
       SET @dEndE = @dDeltaE + @p_dStartE;
       SET @dEndN = @dDeltaN + @p_dStartn;
       RETURN geometry::Point(ROUND(@dEndE, @precision),ROUND(@dEndN, @precision), @srid);
     END;
 END
 GO

Here is an example of how to generate the geometry::Point object at the end of a bearing of 45 degrees and distance of 100 meters from ( 0,0 ) .

 select cogo.PointFromBearingAndDistance(0,0,45,100,3,0).STAsText() as NewPoint;
NewPoint
POINT (70.711 70.711)

Or more visually:

 DECLARE
   @XY Float = SQRT(POWER(100,2)/2);
 select geometry::STGeomFromText('POINT(0 0)',0).STBuffer(10) as geom
 union all
 select geometry::STGeomFromText('LINESTRING(0 0,' + STR(@XY,10,5) + ' ' + STR(@XY,10,5) + ')',0) as geom
 union all
 select cogo.PointFromBearingAndDistance(0,0,45,100,3,0).STBuffer(10) as geom;

I hope this is helpful to someone.