-
Notifications
You must be signed in to change notification settings - Fork 117
ProConcepts COGO
This topic covers the API detailing the classes and methods used to query and edit COGO-enabled line feature classes. For more general information on COGO in ArcGIS Pro, see the help topic Introduction to COGO.
- ArcGIS.Core.dll
- ArcGIS.Desktop.Editing.dll
Language: C#
Subject: COGO
Contributor: ArcGIS Pro SDK Team <[email protected]>
Organization: Esri, http://www.esri.com
Date: 10/07/2021
ArcGIS Pro: 2.7
Visual Studio: 2017, 2019
- Overview
- COGO-enabled lines
- Rules for COGO values
- Convert direction formats and distance units
- Ground to grid corrections
- Create COGO features
- Calculate COGO from geometry
Coordinate Geometry (COGO) is a feature editing technique used primarily in the land records industry. It is characterized by the capturing of the dimensions depicted on land record documents as attributes on line features. These depictions can include the boundaries of land parcels, the centerlines of streets or railroads and the dimensions across road rights of way.
In most cases they are single segment two point line features that model the measurements between point locations. A COGO line feature is most commonly a straight line or a circular arc, but may also be a multi segment polyline when representing a spiral curve, or when representing a natural boundary such as a river or lake shore. Any line feature class may be enhanced to support COGO attributes by making it COGO-enabled.
COGO line features are also used as part of the parcel fabric data model. When a parcel fabric dataset is created, line feature classes are automatically added as COGO-enabled. There is a dedicated API for parcel fabrics. For more information about the Parcel Fabric API see the topic ProConcepts Parcel Fabric.
A COGO line feature class differs from a standard geodatabase feature class in that it has five COGO fields that are added to its existing fields. These five fields have a well-known, predefined schema, as follows:
Field Name | Data Type | Allow NULL |
---|---|---|
Direction | Double | ☑ |
Distance | Double | ☑ |
Radius | Double | ☑ |
ArcLength | Double | ☑ |
Radius2 | Double | ☑ |
You can test to confirm a line feature layer is COGO-enabled as follows:
//first get the feature layer that's selected in the table of contents
var destLineL = MapView.Active.GetSelectedLayers().OfType<FeatureLayer>().FirstOrDefault();
var fcDefinition = destLineL.GetFeatureClass().GetDefinition();
if (fcDefinition.GetShapeType() != GeometryType.Polyline)
return "Please select a line layer in the table of contents.";
bool bIsCOGOEnabled = fcDefinition.IsCOGOEnabled();
You can run the Enable COGO geoprocessing tool to add these fields to a line feature class. Here is some code to make a line feature class COGO-enabled via the Geoprocessing API:
var parameters = Geoprocessing.MakeValueArray("C:\MyFGDB.gdb\MyFDS\MyLineFC");
await Geoprocessing.ExecuteToolAsync("management.EnableCOGO", parameters);
COGO fields are used to store Direction
and Distance
values for straight lines, and Radius
and Arclength
values for circular arcs. Spirals are also supported using the Radius2
field.
There are specific rules for the content in these fields. For example:
- Length units are defined by the spatial reference of the feature class
- Combinations of null and non-null values in the COGO fields for a line feature define the type of line (straight line, circular arc, or spiral).
These rules are covered in more detail in the following section.
The values stored in the Distance
, Radius
, Arclength
, and Radius2
fields are in the linear units of the projection defined on the feature class of the COGO-enabled line. When the feature class does not have a projection but is in a geographic coordinate system, the values are in meters.
The code below uses the feature class definition to get the unit conversion factor:
double dMetersPerUnit = 1;
var fcDefinition = destLineL.GetFeatureClass().GetDefinition();
if (fcDefinition.GetSpatialReference().IsProjected)
dMetersPerUnit = fcDefinition.GetSpatialReference().Unit.ConversionFactor;
This conversion factor is meters per unit. For example if the projection is in International Feet, then this value will be returned as 0.3048
.
Values stored in the Direction
field are in decimal degrees, from 0°
to 360°
. The direction format is North Azimuth, meaning 0°
points north, and angles increase clockwise. For example, northwest is 45°
, south is 180°
and northeast is 315°
.
Note: ArcGIS Pro presents these directions in the user interface in the formats that have been configured for the fields via arcade expressions, labeling and so on.
When passing direction values into functions in the geometry engine, these direction units need to be converted. In most parts of the geometry engine the polar cartesian direction system is used. The polar cartesian system uses angles in radians, with values ranging from -PI
to PI
(or 0
to 2PI
), 0 radians
points east and angles increase counter-clockwise. For example, northeast is PI/4
, south is -PI/2
(or 3PI/2
) and northwest is 3PI/4
. To learn more about different direction systems see the help topic Direction formats for editing.
To see more API information for direction format conversion see the section topic Convert direction formats and distance units.
The following table describes the fields used to store the parameters for each COGO line type:
COGO Line type | Direction | Distance | Radius | Arclength | Radius2 |
---|---|---|---|---|---|
Straight line | ☑ | ☑ | null | null | null |
Circular arc | ☑ | null | ☑ | ☑ | null |
Spiral curve | ☑ | null | ☑ | ☑ | ☑ |
Polylines | ☑ | ☑ | null | null | null |
- A straight line may only have its
Direction
andDistance
COGO fields populated, and the others must be left unused. - A circular arc may only have its
Direction
,Radius
andArclength
COGO fields populated, and the others must be left unused. - A spiral must have its
Direction
,Radius
,Arclength
, andRadius2
COGO fields populated. - A polyline that represents a natural boundary may only have its
Direction
andDistance
COGO fields populated. It is common for all fields to be left as unused for these COGO line types. (Natural boundaries are typically presented in bounds descriptions with wording such as “…bounded on the north by Rose Creek…” without any dimension information).
Any of the values represented by the check boxes in the table above may be left as null. For example, you may have a straight line with the Distance
field containing a value, but with the Direction
field left unused.
The Direction
field on straight lines, circular arcs, and spirals always represents the north azimuth direction along the straight line or chord from start point to end point of the feature. The following code snippet shows how to get the direction info for the straight line between the first and last vertex of a line feature.
ICollection<Segment> LineSegments = new List<Segment>();
myLineFeature.GetAllSegments(ref LineSegments);
int numSegments = LineSegments.Count;
IList<Segment> iList = LineSegments as IList<Segment>;
Segment FirstSeg = iList[0];
Segment LastSeg = iList[numSegments - 1];
var pLine = LineBuilder.CreateLineSegment(FirstSeg.StartCoordinate, LastSeg.EndCoordinate);
var dDirectionPolarRadians = pLine.Angle;
var dDistance = pLine.Length;
In this code note that the direction variable’s value is in radians and is represented in the polar cartesian format used by the geometry engine. To learn more about how this value is converted to north azimuth see Convert direction formats and distance units.
Note in the code above, the straight line distance is the uncorrected value, prior to any unit conversions or ground to grid corrections. For more information about when and how to apply corrections for ground to grid see Ground to grid corrections.
Circular arcs and spirals use an arclength value as one of the parameters to define their shapes. The arclength is always greater than the chord length. Like the other length parameters (radius and distance) the scale factor portion of the ground to grid correction needs to be taken into account when creating tools that read or write the geometry and COGO attributes for COGO lines. For more information about when and how to apply corrections for ground to grid see Ground to grid corrections.
Circular arcs and spirals are defined as turning to the left (counter-clockwise) or turning to the right (clockwise). For defining a circular arc that is proceeding counter-clockwise, the value stored in the Radius
field must be negative, and conversely circular arcs to the right must store positive radius values.
The following code shows how to test the geometry of an arc, and change the sign of the radius attribute based on the IsCounterClockwise
boolean flag.
var MyCircularArcRadiusAttribute = ArcGeometry.IsCounterClockwise ?
- ArcGeometry.SemiMajorAxis : Math.Abs(ArcGeometry.SemiMajorAxis); //radius
Note that all length parameters: arclength, radius, distance, and radius2, are affected by the scale factor used in ground to grid corrections. This needs to be accounted for when creating tools that read or write the geometry and COGO attributes for COGO lines. For more information about when and how to apply corrections for ground to grid see Ground to grid corrections.
The second radius parameter is used exclusively for clothoid spirals. It is used in combination with the first radius value and arclength to define the mathematical shape of the spiral.
Since the geometry engine does not have a true parametric representation for spirals, the geometry can only be approximated by a polyline with a series of short straight line segments. The mathematical representation of the spiral can be rehydrated from its stored COGO attributes. For more information on this technique, see Create a spiral curve.
Another important property of a spiral is that either the start or the end of the curve can have a radius that’s defined as infinity. The rule for defining an infinite radius on a spiral is to use a zero value as follows:
- starting radius of infinity, store
0
forRadius
field. - ending radius of infinity, store
0
forRadius2
field. - a zero value in both radius parameter fields for the same feature is not valid.
As noted in the section about circular arcs, the sign of the radius value defines whether the curve is turning to the left (counter-clockwise) or turning to the right (clockwise). For spirals, since there are two radius values and since one or the other may be a zero value, the left turning spiral is defined if either radius value is negative, or if both values are negative. For example, if one of the values is greater than zero and the other is less than zero, then the spiral is turning to the left (counter-clockwise). Similarly, if both radii are negative then that spiral is also turning left.
For more information about the properties of the clothoid and the API see the reference guide for the PolyLineBuilder constructor.
The geometry engine’s direction format is polar(cartesian) radians whereas the COGO base unit for directions is north azimuth decimal degrees. The code below shows a commonly used direction format conversion function:
private static double PolarRadiansToNorthAzimuthDecimalDegrees(double InPolarRadians)
{
var AngConv = DirectionUnitFormatConversion.Instance;
var ConvDef = new ConversionDefinition()
{
DirectionTypeIn = ArcGIS.Core.SystemCore.DirectionType.Polar,
DirectionUnitsIn = ArcGIS.Core.SystemCore.DirectionUnits.Radians,
DirectionTypeOut = ArcGIS.Core.SystemCore.DirectionType.NorthAzimuth,
DirectionUnitsOut = ArcGIS.Core.SystemCore.DirectionUnits.DecimalDegrees
};
return AngConv.ConvertToDouble(InPolarRadians, ConvDef);
}
This function uses the DirectionUnitFormatConversion
class. An instance of that class is created and then you define a ConversionDefinition
object with specific conversion properties. The incoming direction type and direction units are in this case Polar
and Radians
, and the outgoing direction type and directions units are specified as NorthAzimuth
directions in units of DecimalDegrees
.
Another commonly required conversion is to go from string representations such as quadrant bearings in degrees minutes and seconds, for example N10-59-59E
, into the geometry engine’s polar radians equivalent. The following function follows a similar pattern as the previous example:
private static double QuadrantBearingDMSToPolarRadians(string InQuadBearingDMS)
{
var AngConv = DirectionUnitFormatConversion.Instance;
var ConvDef = new ConversionDefinition()
{
DirectionTypeIn = ArcGIS.Core.SystemCore.DirectionType.QuadrantBearing,
DirectionUnitsIn = ArcGIS.Core.SystemCore.DirectionUnits.DegreesMinutesSeconds,
DirectionTypeOut = ArcGIS.Core.SystemCore.DirectionType.Polar,
DirectionUnitsOut = ArcGIS.Core.SystemCore.DirectionUnits.Radians
};
return AngConv.ConvertToDouble(InQuadBearingDMS, ConvDef);
}
Though the geometry engine mostly uses cartesian directions, there is an exception when using the vector-based computation functions. In these cases the directions are in north azimuth radians. For example, in the following code MyVectorDirection
is in north azimuth radians.
Coordinate3D MyPoint = new Coordinate3D();
MyPoint.SetPolarComponents(MyVectorDirection, 0, MyDistance);
The following function can be used to convert from polar radians to north azimuth, also in radians.
private double PolarRadiansToNorthAzimuthRadians(double InPolarRadians)
{
var AngConv = DirectionUnitFormatConversion.Instance;
var ConvDef = new ConversionDefinition()
{
DirectionTypeIn = ArcGIS.Core.SystemCore.DirectionType.Polar,
DirectionUnitsIn = ArcGIS.Core.SystemCore.DirectionUnits.Radians,
DirectionTypeOut = ArcGIS.Core.SystemCore.DirectionType.NorthAzimuth,
DirectionUnitsOut = ArcGIS.Core.SystemCore.DirectionUnits.Radians
};
return AngConv.ConvertToDouble(InPolarRadians, ConvDef);
}
Many land records documents define a two-dimensional planar coordinate system representing a relatively small area of land such as a subdivision for multiple parcels, or a deed for an individual parcel. These localized “ground” coordinate systems minimize the distortion that would otherwise result from projecting data into the “grid” coordinate systems that are designed to model much larger areas.
These ground coordinate systems have the practical advantage of making each land record document independent of any particular projection so that the directions and distances can stand independent of grid coordinates. In fact many land records do not document coordinates at all, while others may provide grid coordinates and reference a projection but will still record distance and direction values in a ground system.
In cases where coordinates are documented, they will usually also include ground to grid correction information that is specific to the coordinate system. For example, a subdivision may include wording such as in the following notes:
- “The coordinates shown hereon are Texas South Central Zone No.4204 State Plane Grid Coordinates (NAD83) and may be brought to surface by dividing by the combined scale factor of 0.999880014.”
- “All bearings are based on the Texas Coordinate System, South Central Zone (4204).”
In note 1 the term “surface” refers to the ground coordinate system. The 0,0 origin of the Texas Coordinate System should be used as the fixed anchor point when scaling the coordinates. For this example, the resulting surface (ground) coordinates would be offset to the northeast of the coordinates in the projected grid system, and the resulting lengths between these surface coordinates would model horizontal ground distances. If a different projection were used, then the scale factor would also need to be changed to achieve the same horizontal ground distances. The second note indicates that the bearings have no angular offset, and so in this case there is no angle offset correction required for directions.
For more information about ground to grid corrections, including what is meant by the “combined” factor, see the help topic Ground to grid correction.
In ArcGIS Pro the ground to grid corrections are stored on a per-map basis; each map has its own corrections. You get the map’s ground to grid correction object from its CIM
definition. In the following code, the active map view is used to get the map’s ground to grid correction object:
//Get the active map view.
var mapView = MapView.Active;
if (mapView?.Map == null)
return;
var cimDefinition = mapView.Map?.GetDefinition();
if (cimDefinition == null) return;
var cimG2G = cimDefinition.GroundToGridCorrection;
For new maps the GroundToGridCorrection
object needs to be created. In the following code we test for a null object and then create it if it’s null:
if (cimG2G == null)
cimG2G = new CIMGroundToGridCorrection();
The primary properties of the ground to grid correction are the distance factor and the direction offset. These properties may be turned on or off individually, and the whole correction object may also be activated or deactivated for each map. In ArcGIS Pro the ground to grid corrections can be seen on the user interface from the Edit ribbon, and also on the status bar located at the bottom of the active map.
For more about using this correction in the user interface see Set ground to grid correction.
Reading the ground to grid properties is done through extension methods on the GroundToGridCorrection
object. These methods automatically check if the ground to grid is active and turned on for the map, and also tests for the state of the individual corrections currently set by the user.
If any of these are not active or turned off then the distance factor is returned with a factor of 1.0000
, and similarly the direction offset correction will return a value of 0.000
if it is not active. This makes the use of these extension methods easy in the API since you don’t need to check the different combinations of properties.
Note that the direction offset correction is always stored in decimal degrees. In the code below it is converted to radians for use in the geometry engine functions:
//These extension methods automatically check if ground to grid is active.
double dScaleFactor = cimG2G.GetConstantScaleFactor();
double dDirectionOffsetCorrection = cimG2G.GetDirectionOffset() * Math.PI / 180;
The API for setting ground to grid does not need extension methods. You set the properties directly as shown in the code below:
cimG2G.UseScale = true; //turn on Distance Factor
cimG2G.ScaleType = GroundToGridScaleType.ConstantFactor; //turn on Constant Scale
cimG2G.ConstantScaleFactor = 0.999880014;
await mapView.Map.SetGroundToGridCorrection(cimG2G);
The preceding code turns on the constant scale factor and assigns a value. The newly updated ground to grid object must be stored on the Map using the SetGroundToGridCorrection
method.
In standard COGO workflows, line features are created from the directions and distances provided by the user who reads them off the land record document. The API pattern for creating these features is to take the provided values, apply the appropriate unit and ground to grid conversions to create the line geometry, and then to store the incoming COGO values, uncorrected, in the COGO attribute fields. The following sections detail these steps for three of the COGO line types.
The most commonly used COGO feature is the single segment two point line. The following code applies the information from the preceding topics to create a straight line COGO feature. Unit conversions and ground to grid corrections are included.
//Example scenario. Make a straight line that has N10°E bearing, and 100 feet.
//=====================================================
//User Entry Values
//=====================================================
string QuadrantBearingDirection = "n10-00-0e";
double dDistance = 100; //in International feet
//=====================================================
double dMetersPerUnit = 1;
if (fcDefinition.GetSpatialReference().IsProjected)
dMetersPerUnit = fcDefinition.GetSpatialReference().Unit.ConversionFactor;
//we know the incoming value is in feet, but since we don’t know what the user’s target linear unit is
//we first convert to metric and then use the metric converter to get the value for the target linear unit
dDistance *= 0.3048; //first convert to metric
//dDistance is now in meters, so divide by meters per unit
//to get the base distance to be stored in the Distance field
dDistance /= dMetersPerUnit;
#region get the ground to grid corrections
//Get the active map view.
var mapView = MapView.Active;
if (mapView?.Map == null)
return;
var cimDefinition = mapView.Map?.GetDefinition();
if (cimDefinition == null) return;
var cimG2G = cimDefinition.GroundToGridCorrection;
//These extension methods automatically check if ground to grid is active, etc.
double dG2G_ScaleFactor = cimG2G.GetConstantScaleFactor();
double dG2G_DirectionOffsetCorrection = cimG2G.GetDirectionOffset() * Math.PI / 180;
//this property is in decimal degrees. Converted to radians for use in circular arc creation
#endregion
double dDirection = QuadrantBearingDMSToPolarRadians(QuadrantBearingDirection);
//using DirectionUnitFormatConversion class
//Now apply the ground to grid corrections
double dGridDistance = dDistance * dG2G_ScaleFactor;
double dGridDirection = dDirection + dG2G_DirectionOffsetCorrection;
var lineStartPoint = MapView.Active.Extent.Center.Coordinate2D; //using the center of the map as a starting point
if (lineStartPoint.IsEmpty)
return;
double vecDirn = PolarRadiansToNorthAzimuthRadians(dGridDirection); //vector constructor uses NorthAzimuth radians
Coordinate3D pVec1 = new Coordinate3D(lineStartPoint.X, lineStartPoint.Y, 0);
Coordinate3D pVec2 = new Coordinate3D();
pVec2.SetPolarComponents(vecDirn, 0, dGridDistance);
Coordinate2D coord2 = new Coordinate2D(pVec1.AddCoordinate3D(pVec2));
var pLine = LineBuilder.CreateLineSegment(lineStartPoint, coord2);
//Create the line geometry
var newPolyline = PolylineBuilder.CreatePolyline(pLine);
Dictionary<string, object> MyAttributes = new Dictionary<string, object>();
MyAttributes.Add(fcDefinition.GetShapeField(), newPolyline);
//check to make sure line is COGO enabled
if (fcDefinition.IsCOGOEnabled())
{
//storing the entered direction in northazimuth decimal degrees
MyAttributes.Add("Direction", PolarRadiansToNorthAzimuthDecimalDegrees(dDirection));
MyAttributes.Add("Distance", dDistance);
}
var op = new EditOperation
{
Name = "Construct Line",
SelectNewFeatures = true
};
op.Create(featLyr, MyAttributes);
op.Execute();
Circular arcs can be created via the API using the EllipticArcSegment
class and the EllipticArcBuilder
. This seems strange at first, but since a circular arc is a special form of an elliptical arc the EllipticArcBuilder
has overloads specifically for creating circular arcs. In the following code we're using a chord length, a chord bearing, a radius and counterclockwise curve (to the left) to create the circular arc.
//Example scenario. Make a circular arc that has 30 meter radius and 30 meter chord, N10°E chord bearing
//=====================================================
//User Entry Values
//=====================================================
double dRadius = 30; //in meters
double dChord = 30; //in meters
string QuadrantBearingChord = "n10-00-0e";
bool curveLeft = true; //curve turning towards the left when traveling from start point towards end point.
//=====================================================
double dMetersPerUnit = 1;
if (fcDefinition.GetSpatialReference().IsProjected)
dMetersPerUnit = fcDefinition.GetSpatialReference().Unit.ConversionFactor;
//We know the incoming length values are already metric, but we don’t know what the user’s target linear unit is.
//We can use the metric converter to get the value for the target linear unit
//These values are the "ground" Radius and Chord values
dRadius /= dMetersPerUnit; //30 meters divided by meters per unit
dChord /= dMetersPerUnit;
double dChordDirection = QuadrantBearingDMSToPolarRadians(QuadrantBearingChord);
esriArcOrientation CCW = curveLeft ? esriArcOrientation.esriArcCounterClockwise : esriArcOrientation.esriArcClockwise;
#region get the ground to grid corrections
//Get the active map view.
var mapView = MapView.Active;
if (mapView?.Map == null)
return;
var cimDefinition = mapView.Map?.GetDefinition();
if (cimDefinition == null) return;
var cimG2G = cimDefinition.GroundToGridCorrection;
//These extension methods automatically check if ground to grid is active, etc.
double dG2G_ScaleFactor = cimG2G.GetConstantScaleFactor();
double dG2G_DirectionOffsetCorrection = cimG2G.GetDirectionOffset() * Math.PI / 180;
//this property is in decimal degrees. Converted to radians for use in circular arc creation
#endregion
double dGridRadius = dRadius * dG2G_ScaleFactor;
double dGridChord = dChord * dG2G_ScaleFactor;
double dGridChordDirection = dChordDirection + dG2G_DirectionOffsetCorrection;
var circArcStartPoint = MapView.Active.Extent.Center;
if (circArcStartPoint == null)
return;
//Create the circular arc geometry
EllipticArcSegment pCircArc = null;
try
{
pCircArc = EllipticArcBuilder.CreateEllipticArcSegment(circArcStartPoint, dGridChord, dGridChordDirection,
dGridRadius, CCW, MinorOrMajor.Minor);
}
catch
{
System.Windows.MessageBox.Show("Circular arc parameters not valid.", "Construct Circular Arc");
return;
}
var newPolyline = PolylineBuilder.CreatePolyline(pCircArc);
Dictionary<string, object> MyAttributes = new Dictionary<string, object>();
MyAttributes.Add(fcDefinition.GetShapeField(), newPolyline);
//check to make sure line is COGO enabled
if (fcDefinition.IsCOGOEnabled())
{
//storing the entered direction in north azimuth decimal degrees
MyAttributes.Add("Direction", PolarRadiansToNorthAzimuthDecimalDegrees(dChordDirection));
if (curveLeft)
dRadius = -dRadius; //store curves to the left with negative radius.
MyAttributes.Add("Radius", dRadius);
//the arclength on the geometry is grid, so convert to ground
MyAttributes.Add("Arclength", pCircArc.Length / dG2G_ScaleFactor);
}
var op = new EditOperation
{
Name = "Construct Circular Arc",
SelectNewFeatures = true
};
op.Create(featLyr, MyAttributes);
op.Execute();
Note in the code above that even though the circular arc is defined by the user with a chord distance, the arclength is used when writing the COGO attributes. Regardless of the entry parameters used to construct the circular arc, the mathematical equivalent for the arclength, radius and chord direction are always written as COGO attributes using full precision of the double fields. In this manner any other circular arc value can be recovered. For example, if the original circular was created by the user with a delta angle, that same delta angle value can be re-computed from the stored COGO attributes.
Similarly, when constructing the geometry for the circular arc you can get the different parameter combinations by using first principle circular arc geometry. For example, in the code below we're creating a circular arc using a delta, arclength and the chord direction. The radius and chord length are pre-computed from the arc length and delta (also called the central angle). These are then used in the circular arc constructor.
//get the radius and chord from the central angle and arclength
MyRadius = MyArcLength / MyDelta;
MyChordDistance = 2 * MyRadius * Math.Sin(MyDelta / 2);
MinorOrMajor MinMaj = MyDelta > Math.PI ? MinorOrMajor.Major : MinorOrMajor.Minor;
MyCircularArcSegment = EllipticArcBuilder.CreateEllipticArcSegment(MyStartPoint, MyChordDistance, MyChordDirection, MyRadius, CCW, MinMaj, null);
The following code snippet calculates the radius from the chord length and delta:
dRadius = 0.5 * dChord / Math.Sin(dDelta / 2);
MinorOrMajor MinMaj = dDelta > Math.PI ? MinorOrMajor.Major : MinorOrMajor.Minor;
The orientaion of circular arcs are usually defined using one of three types of directions: tangent direction, chord direction, or radial direction.
This code snippet shows how to get the chord direction from the tangent direction, chord length and radius:
double dHalfDelta = Math.Abs(Math.Asin(dChord / (2 * dRadius)));
//get chord bearing from given tangent direction in polar radians
if (CCW == esriArcOrientation.esriArcCounterClockwise)
dChordDirection = dTangentDirection + dHalfDelta;
else
dChordDirection = dTangentDirection - dHalfDelta;
MinorOrMajor MinMaj = dHalfDelta > Math.PI / 2 ? MinorOrMajor.Major : MinorOrMajor.Minor;
Some circular arcs are presented with a radial direction as an entry parameter. This code snippet shows how to get the chord direction from the radial direction, radius and chord length:
double dHalfDelta = Math.Abs(Math.Asin(dChord / (2 * dRadius)));
//get chord direction from given radial direction in north azimuth radians
if (CCW == esriArcOrientation.esriArcCounterClockwise)
dChordDirection = dRadialDirection - Math.PI / 2 + dHalfDelta;
else
dChordDirection = dRadialDirection + Math.PI / 2 - dHalfDelta;
MinorOrMajor MinMaj = dHalfDelta > Math.PI / 2 ? MinorOrMajor.Major : MinorOrMajor.Minor;
The creation of a spiral feature follows a similar pattern as described above. The key difference with the spiral is that the geometry engine is only able to approximate the shape by a connected sequence of straight line segments. The nature of the approximation is defined during the construction of the spiral. Since the spiral is approximated by a polyline, the PolylineBuilder
object is used.
Polyline mySpiral = PolylineBuilder.CreatePolyline(StartPoint, TangentDirection, StartRadius,
EndRadius, orientation, createMethod, ArcLength, densifyMethod, densifyParameter, spatialReference);
While circular arcs are parametrically defined by the geometry engine, the spiral can only be detected by the presence of COGO attributes, specifically when the Radius2
field is populated. The COGO feature stored is a multi-segment polyline and is not the true mathematical representation of the spiral. The stored COGO attributes must be used to rehydrate the mathematical representation of the spiral prior to computations like getting the tangent line or orthogonal offsets.
The following function applies this technique to find the tangent point, radius, tangent direction, length, and delta angle at a specific distance along the mathematical spiral defined by the five input constructor parameters. These constructor parameters are read from the COGO attributes of the spiral feature.
For further explanation of this code, see the note that follows.
private bool QuerySpiralParametersByArclength(double queryLength, MapPoint constructorStartPoint, double constructorTangentDirection,
double constructorStartRadius, double constructorEndRadius, double constructorArcLength, SpatialReference spatRef,
out MapPoint tangentPointOnPath, out double radiusCalculated, out double tangentDirectionCalculated, out double lengthCalculated,
out double deltaAngleCalculated)
{//Returns a point on a constructed spiral that is at the given distance along that constructed spiral.
//Returns the following at this station: tangent point, tangent direction, radius, delta angle
esriArcOrientation orientation = esriArcOrientation.esriArcClockwise;
if (constructorStartRadius < 0 || constructorEndRadius < 0)
orientation = esriArcOrientation.esriArcCounterClockwise;
esriClothoidCreateMethod createMethod = esriClothoidCreateMethod.ByLength;
esriCurveDensifyMethod densifyMethod = esriCurveDensifyMethod.ByLength;
double densifyParameter = constructorArcLength / 5000; //densification has an upper limit of 5000 vertices
Polyline mySpiral = PolylineBuilder.CreatePolyline(constructorStartPoint, constructorTangentDirection, constructorStartRadius,
Math.Abs(constructorEndRadius), orientation, createMethod, constructorArcLength, densifyMethod, densifyParameter, spatRef);
int numPoints = mySpiral.PointCount; //this has an upper limit with small densify parameter methods. 5000 points
if (queryLength > constructorArcLength)
queryLength = constructorArcLength;
int idxOfClosestQueryPoint = Convert.ToInt32(Math.Floor(queryLength / constructorArcLength * numPoints)) - 1;
if (idxOfClosestQueryPoint < 0)
idxOfClosestQueryPoint = 0;
MapPoint queryPointAtArcLength = mySpiral.Points[idxOfClosestQueryPoint]; //query point at arclength query distance
double radius01_Calculated, tangentDirection01_Calculated, length01_Calculated, deltaAngle01_Calculated;
MapPoint tangentPointOnPath01=null, tangentPointOnPath02=null;
PolylineBuilder.QueryClothoidParameters(queryPointAtArcLength, constructorStartPoint, constructorTangentDirection,
Math.Abs(constructorStartRadius), Math.Abs(constructorEndRadius), orientation, createMethod, constructorArcLength,
out tangentPointOnPath01, out radius01_Calculated, out tangentDirection01_Calculated,
out length01_Calculated, out deltaAngle01_Calculated, spatRef);
//test out arc length of this first densification point against our requested arc length
double smallArcLength = 0;
if (length01_Calculated < queryLength) //should always be the case, unless they're exactly equal
smallArcLength = queryLength - length01_Calculated;
if (smallArcLength > 0 && ((numPoints-1) != idxOfClosestQueryPoint))
{
queryPointAtArcLength = mySpiral.Points[idxOfClosestQueryPoint + 1];
PolylineBuilder.QueryClothoidParameters(queryPointAtArcLength, constructorStartPoint, constructorTangentDirection,
Math.Abs(constructorStartRadius), Math.Abs(constructorEndRadius), orientation, createMethod, constructorArcLength,
out tangentPointOnPath02, out radiusCalculated, out tangentDirectionCalculated, out lengthCalculated, out deltaAngleCalculated, spatRef);
//go smallArcLength distance from tangentPointOnPath01 in the direction tangentDirection01_Calculated
Coordinate3D TangPt01 = new Coordinate3D(tangentPointOnPath01);
Coordinate3D TangPt02 = new Coordinate3D();
TangPt02.SetPolarComponents(PolarRadiansToNorthAzimuthDecimalDegrees(tangentDirection01_Calculated) * Math.PI/180, 0, smallArcLength);
MapPoint queryPointRefined = TangPt01.AddCoordinate3D(TangPt02).ToMapPoint(spatRef);
double SmallSpiralArcLength = (lengthCalculated - queryLength) + smallArcLength;
//construct and query the small refined spiral between the 2 densification points
PolylineBuilder.QueryClothoidParameters(queryPointRefined, tangentPointOnPath01, tangentDirection01_Calculated,
Math.Abs(radius01_Calculated), Math.Abs(radiusCalculated), orientation, createMethod, SmallSpiralArcLength,
out tangentPointOnPath, out radiusCalculated, out tangentDirectionCalculated, out lengthCalculated, out deltaAngleCalculated, spatRef);
//test the computed tangentPointOnPath against the refined query point
double xDiff = (queryPointRefined.X - tangentPointOnPath.X);
double yDiff = (queryPointRefined.Y - tangentPointOnPath.Y);
double dDist = Math.Sqrt(((xDiff * xDiff) + (yDiff * yDiff)));
//NOTE: input tangent point on path is updated on out parameter
PolylineBuilder.QueryClothoidParameters(tangentPointOnPath, constructorStartPoint, constructorTangentDirection,
Math.Abs(constructorStartRadius), Math.Abs(constructorEndRadius), orientation, createMethod, constructorArcLength,
out tangentPointOnPath, out radiusCalculated, out tangentDirectionCalculated, out lengthCalculated, out deltaAngleCalculated, spatRef);
}
else
{
radiusCalculated = radius01_Calculated;
tangentDirectionCalculated = tangentDirection01_Calculated;
lengthCalculated = length01_Calculated;
deltaAngleCalculated = deltaAngle01_Calculated;
tangentPointOnPath = tangentPointOnPath01;
}
return true;
}
Note that the vertices on the geometry are exactly on the mathematical course of the spiral, but the line segments between the vertices are not. Using the distance along the spiral, the two closest vertices on either side of the station length are used to query the radius values at each of those locations so that another mini-spiral can be contructed between those same two known points along the spiral curve, and this allows a "concentrated" densely defined spiral to get the most precision possible.
In certain COGO workflows such as copying line work from CAD, you can have two-point lines in a COGO-enabled feature class without COGO attributes. This means that rather than creating the geometry and assigning COGO attributes in the same operation, the COGO attributes are assigned using the geometry of the already stored line feature. In the Pro UI this is done by running the editing tool called Update COGO
. To do this work in code the following function can be used to calculate the COGO attribute values from the geometry:
private bool GetCOGOFromGeometry(Polyline myLineFeature, SpatialReference MapSR, double ScaleFactor,
double DirectionOffset, out object[] COGODirectionDistanceRadiusArcLength)
{
COGODirectionDistanceRadiusArcLength = new object[4] { DBNull.Value,DBNull.Value,DBNull.Value, DBNull.Value };
try
{
COGODirectionDistanceRadiusArcLength[0] = DBNull.Value;
COGODirectionDistanceRadiusArcLength[1] = DBNull.Value;
var GeomSR= myLineFeature.SpatialReference;
if (GeomSR.IsGeographic && MapSR.IsGeographic)
return false; //Future work: Make use of API for Geodesics.
double UnitConversion = 1;
if (GeomSR.IsGeographic && MapSR.IsProjected)
{ //only need to project if dataset is in a GCS.
UnitConversion = MapSR.Unit.ConversionFactor; // Meters per unit. Only need this for converting to metric for GCS datasets.
myLineFeature = GeometryEngine.Instance.Project(myLineFeature, MapSR) as Polyline;
}
EllipticArcSegment pCircArc;
ICollection<Segment> LineSegments = new List<Segment>();
myLineFeature.GetAllSegments(ref LineSegments);
int numSegments = LineSegments.Count;
IList<Segment> iList = LineSegments as IList<Segment>;
Segment FirstSeg = iList[0];
Segment LastSeg = iList[numSegments - 1];
var pLine = LineBuilder.CreateLineSegment(FirstSeg.StartCoordinate, LastSeg.EndCoordinate);
COGODirectionDistanceRadiusArcLength[0] =
PolarRadiansToNorthAzimuthDecimalDegrees(pLine.Angle - DirectionOffset*Math.PI/180);
COGODirectionDistanceRadiusArcLength[1] = pLine.Length * UnitConversion / ScaleFactor;
//check if the last segment is a circular arc
var pCircArcLast = LastSeg as EllipticArcSegment;
if (pCircArcLast == null)
return true; //we already know there is no circluar arc COGO
//Keep a copy of the center point
var LastCenterPoint = pCircArcLast.CenterPoint;
COGODirectionDistanceRadiusArcLength[2] = pCircArcLast.IsCounterClockwise ?
-pCircArcLast.SemiMajorAxis : Math.Abs(pCircArcLast.SemiMajorAxis); //radius
double dArcLengthSUM = 0;
//use 30 times xy tolerance for circular arc segment tangency test
double dTangencyToleranceTest = MapSR.XYTolerance * 30; //around 3cms if using default XY Tolerance - recommended
for (int i = 0; i < numSegments; i++)
{
pCircArc = iList[i] as EllipticArcSegment;
if (pCircArc == null)
{
COGODirectionDistanceRadiusArcLength[2] = DBNull.Value; //radius
COGODirectionDistanceRadiusArcLength[3] = DBNull.Value; //arc length
return true;
}
var tolerance = LineBuilder.CreateLineSegment(LastCenterPoint, pCircArc.CenterPoint).Length;
if (tolerance > dTangencyToleranceTest)
{
COGODirectionDistanceRadiusArcLength[2] = DBNull.Value; //radius
COGODirectionDistanceRadiusArcLength[3] = DBNull.Value; //arc length
return true;
}
dArcLengthSUM += pCircArc.Length; //arc length sum
}
//now check to see if the radius and arclength survived and if so, clear the distance
if (COGODirectionDistanceRadiusArcLength[2] != DBNull.Value)
COGODirectionDistanceRadiusArcLength[1] = DBNull.Value;
COGODirectionDistanceRadiusArcLength[3] = dArcLengthSUM * UnitConversion / ScaleFactor;
COGODirectionDistanceRadiusArcLength[2] = (double)COGODirectionDistanceRadiusArcLength[2] * UnitConversion / ScaleFactor;
return true;
}
catch
{
return false;
}
}
Home | API Reference | Requirements | Download | Samples
- Overview of the ArcGIS Pro SDK
- What's New for Developers at 3.4
- Installing ArcGIS Pro SDK for .NET
- Release notes
- Resources
- Pro SDK Videos
- ProSnippets
- ArcGIS Pro API
- ProGuide: ArcGIS Pro Extensions NuGet
Migration
- ProSnippets: Framework
- ProSnippets: DAML
- ProConcepts: Framework
- ProConcepts: Asynchronous Programming in ArcGIS Pro
- ProConcepts: Advanced topics
- ProGuide: Custom settings
- ProGuide: Command line switches for ArcGISPro.exe
- ProGuide: Reusing ArcGIS Pro Commands
- ProGuide: Licensing
- ProGuide: Digital signatures
- ProGuide: Command Search
- ProGuide: Keyboard shortcuts
Add-ins
- ProGuide: Installation and Upgrade
- ProGuide: Your first add-in
- ProGuide: ArcGIS AllSource Project Template
- ProConcepts: Localization
- ProGuide: Content and Image Resources
- ProGuide: Embedding Toolboxes
- ProGuide: Diagnosing ArcGIS Pro Add-ins
- ProGuide: Regression Testing
Configurations
Customization
- ProGuide: The Ribbon, Tabs and Groups
- ProGuide: Buttons
- ProGuide: Label Controls
- ProGuide: Checkboxes
- ProGuide: Edit Boxes
- ProGuide: Combo Boxes
- ProGuide: Context Menus
- ProGuide: Palettes and Split Buttons
- ProGuide: Galleries
- ProGuide: Dockpanes
- ProGuide: Code Your Own States and Conditions
Styling
- ProSnippets: Content
- ProSnippets: Browse Dialog Filters
- ProConcepts: Project Content and Items
- ProConcepts: Custom Items
- ProGuide: Custom Items
- ProGuide: Custom browse dialog filters
- ArcGIS Pro TypeID Reference
- ProSnippets: Editing
- ProConcepts: Editing
- ProConcepts: COGO
- ProConcepts: Annotation Editing
- ProConcepts: Dimension Editing
- ProGuide: Editing Tool
- ProGuide: Sketch Tool With Halo
- ProGuide: Construction Tools with Options
- ProGuide: Annotation Construction Tools
- ProGuide: Annotation Editing Tools
- ProGuide: Knowledge Graph Construction Tools
- ProGuide: Templates
3D Analyst Data
Plugin Datasources
Topology
Linear Referencing
Object Model Diagram
- ProSnippets: Geometry
- ProSnippets: Geometry Engine
- ProConcepts: Geometry
- ProConcepts: Multipatches
- ProGuide: Building Multipatches
Relational Operations
- ProSnippets: Knowledge Graph
- ProConcepts: Knowledge Graph
- ProGuide: Knowledge Graph Construction Tools
Reports
- ProSnippets: Map Authoring
- ProSnippets: Annotation
- ProSnippets: Charts
- ProSnippets: Labeling
- ProSnippets: Renderers
- ProSnippets: Symbology
- ProSnippets: Text Symbols
- ProConcepts: Map Authoring
- ProConcepts: Annotation
- ProConcepts: Dimensions
- ProGuide: Tray buttons
- ProGuide: Custom Dictionary Style
- ProGuide: Geocoding
3D Analyst
CIM
Graphics
Scene
Stream
Voxel
- ProSnippets: Map Exploration
- ProSnippets: Custom Pane with Contents
- ProConcepts: Map Exploration
- ProGuide: Map Pane Impersonation
- ProGuide: TableControl
Map Tools
- ProGuide: Feature Selection
- ProGuide: Identify
- ProGuide: MapView Interaction
- ProGuide: Embeddable Controls
- ProGuide: Custom Pop-ups
- ProGuide: Dynamic Pop-up Menu
Network Diagrams
- ArcGIS Pro API Reference Guide
- ArcGIS Pro SDK (pro.arcgis.com)
- arcgis-pro-sdk-community-samples
- ArcGISPro Registry Keys
- ArcGIS Pro DAML ID Reference
- ArcGIS Pro Icon Reference
- ArcGIS Pro TypeID Reference
- ProConcepts: Distributing Add-Ins Online
- ProConcepts: Migrating to ArcGIS Pro
- FAQ
- Archived ArcGIS Pro API Reference Guides
- Dev Summit Tech Sessions