This document is archived and information here might be outdated.  Recommended version.


Location

Location


Supported with:
  • Engine
  • ArcGIS for Desktop Basic
  • ArcGIS for Desktop Standard
  • ArcGIS for Desktop Advanced
  • Server
Library dependencies: Version, System, SystemUI, Geometry, GraphicsCore, Display, Server, Output, Geodatabase, GISClient, DataSourcesFile, DataSourcesGDB, DataSourcesOleDB, DataSourcesRaster, DataSourcesNetCDF, GeoDatabaseDistributed, GeoDatabaseExtensions, Carto, NetworkAnalysis

Additional library information: Contents, Object Model Diagram

The Location library contains objects that support geocoding and linear referencing.

See the following sections for more information about this namespace:

About geocoding

The geocoding objects provide a framework for creating and managing address locators. Locator objects create geometry for non-spatial descriptions of locations. An address locator is a particular type of locator that creates geometries for text representing addresses. Address locators can be stored on disk or in a file, personal, or ArcSDE geodatabase. Address locators can also be used to create GeocodeServer objects that are served using ArcGIS for Server. The geocoding objects also provide an extensible framework for creating new types of custom locators.

Locator workspace objects in geocoding

A locator workspace contains locators and templates for creating locators or locator styles. The LocatorWorkspace abstract class allows you to create, store, and retrieve its locators and locator styles. The following illustration shows the locator workspace objects:

For further information see:

How to open locator workspaces
Working with locators and locator styles

Locator styles in geocoding

Each locator style class supports the required interfaces to specify the reference data and geocoding options for a locator object. To create a locator, a developer retrieves the locator style on which the new locator is based from a locator workspace, specifies the properties on the style as required to define the new locator, and stores the modified locator style in the locator workspace using the AddLocator method on the ILocatorWorkspace interface.
Locators and locator styles support the ISimpleStandardization interface, which can be used to standardize single addresses or tables and feature classes containing address information. You can use the SimpleStandardizeTable method to standardize the address information in a feature class that will be used as reference data for a locator. It is a good idea to standardize the address information in reference data feature classes so that address locators created using those reference data feature classes will standardize addresses in the same way they are standardized in the reference data.

For further information see:

Creating an address locator
How to standardize an address

Locator reference data in geocoding

The reference data objects are used to specify and manage the reference data used by ESRIGen2AddressLocator. The following illustration shows the reference data objects:
Each ESRIGen2AddressLocator has a ReferenceDataTableEnumerator, which is an enumeration of ReferenceDataTable objects used by the locator. Use the Tables property on the IReferenceDataTables interface on the locator or locator style to get the ReferenceDataTableEnumerator.
ReferenceDataTable objects are used to specify the data sources that ESRIGen2AddressLocator uses. The Name property on the IReferenceDataTable interface returns a reference to a TableName object that represents the data source used as reference data by the locator. Each ReferenceDataTable has a ReferenceDataIndexEnumerator, which specifies the geocoding indexes that an ESRIGen2AddressLocator uses to quickly find potential candidates for addresses. ReferenceDataTable objects also have enumerations of ReferenceDataField objects, which are used to specify the fields in the data source that contain address information.
For an example that shows how to use the locator workspace, locator style, and locator reference data objects to create address locators, see Creating an address locator.

Address locators in geocoding

Locator is an abstract class that creates geometry for non-spatial descriptions of locations. All types of locators, including address locators and route locators, are subclasses of the Locator abstract class. The following illustration shows the locator objects:
AddressLocator objects are a type of locator that create geometry for text descriptions of addresses. This process is commonly called geocoding. Address locators geocode tables of addresses, find single addresses, and find candidates for addresses.
The IAddressGeocoding interface is the primary interface used to geocode addresses. It can be used to geocode a single address or a table of addresses, such as one in ArcSDE.
Reverse geocoding can be used through the IReverseGeocoding and IReverseGeocodingProperties interfaces to find the address closest to a point.
IBatchGeocoding provides access to additional geocoding functionality. IBatchGeocoding can be used to match a recordSet or rematch a geocoded feature class with modified locator properties if you are not satisfied with the results after geocoding a table of addresses.

For further information see:

How to geocode a single address
How to geocode a table of addresses
How to rematch a geocoded feature class
How to find the address closest to a point using reverse geocoding
Sample: Find an address
Sample: Find the closest intersection from a point

GeocodeServer

GeocodeServer is a ServerObject served by an ArcGIS Server that can be used to geocode addresses. Internally, GeocodeServer uses an address locator to geocode and exposes the high-level functionality of the address locator using the IGeocodeServer interface. Generally, GeocodeServer objects are used by ArcGIS for Server developers to create server applications that include geocoding functionality. ArcGIS for Desktop and ArcGIS Engine developers can, however, use GeocodeServer objects to include geocoding functionality in ArcGIS for Desktop customizations and custom applications using only the high-level geocoding functionality exposed by GeocodeServer.

For further information see:

How to geocode a single address

About linear referencing

The linear referencing objects provide a framework for the creation, management, and display of linearly referenced data. These objects allow you to find and identify route locations, as well as dynamically segment route events so they can be displayed on a map. Additional objects are provided for both route and event geoprocessing, as well as for the enhanced cartographic display of routes and events.

Dynamic segmentation in linear referencing

Dynamic segmentation is the process of computing the shape of route locations along calibrated linear features at run time based on event tables for which distance measures are available. A calibrated linear feature, or route, is a polyline feature that has m-values (measure) and an identifier.
Route locations can be organized into tables based on a common theme. These are called event tables. For example, five event tables containing information on speed limits, year of resurfacing, present conditions, signs, and accidents can reference a route feature class representing highways.
An event table is any table that contains a route identifier field and at least one measure field. Tables containing point route locations have one measure field, while tables containing linear route locations have two. The route identifier field matches the route identifier in the route feature class (they do not have to have the same name).
A route event source serves an event table as a "dynamic" feature class. Each row in the table is served as a feature whose shape is calculated on-the-fly every time it is requested. This is dynamic segmentation.
This dynamic segmentation concept is shown in the following illustration:
In a route event source, there is one feature for every row of the original event table. Sometimes, however, the features have empty shapes. This is because there was some reason the event could not be properly located. In other instances, an event can only be partially located (this happens only with line events). The following illustration shows some sample error code:
The following illustration shows the dynamic segmentation objects:
Locator and RouteLocator
Locator is an abstract class that specifies the interfaces common to all types of locator objects. Types of locators include addresses, x,y coordinates, routes, and place names. Locators combine reference data and a location method.
The ILocator interface provides access to the properties of a locator.
The ILocatorFullName interface provides access to the name property of a locator so that it can be persisted.
RouteLocator is an abstract class. RouteLocator can transform a route location into a shape that can be displayed on a map. The Locator and RouteLocator abstract classes are shown in the following illustration:
Route locations describe a precise location along a route or a portion of a route between a from- and to-measure.
The IRouteLocator2 interface inherits from IRouteLocator and is useful for retrieving the properties of a RouteLocator object, and for determining the shape of route locations and events (see the example following the discussion on route locations). The Identify method is used to identify route locations using an envelope. When using ArcMap, a good way to create this envelope is to use the envelope of the map document’s current location expanded by the search tolerance. See the following code example:
[C#]
IEnvelope envelope=mxDoc.CurrentLocation.Envelope;
envelope.Expand(mxDoc.SearchTolerance, mxDoc.SearchTolerance, false);
[VB.NET]
Dim envelope As IEnvelope=mxDoc.CurrentLocation.Envelope
envelope.Expand(mxDoc.SearchTolerance, mxDoc.SearchTolerance, False)
For route locators, the RouteFeatureClass property can be a coverage route system, a PolyLineM shapefile, or a PolyLine feature class (with m-values) in a personal, file, or ArcSDE geodatabase. This means routes are stored in a feature class where IGeometryDef.GeometryType=esriGeometryPolyLine and IGeometryDef.HasM=true.

RouteMeasureLocator and RouteMeasureLocatorName

RouteMeasureLocator is one type of RouteLocator. It determines the shape of a route location by matching the route location’s measure values to those stored in a route feature. RouteMeasureLocator is created via its Name object counterpart, RouteMeasureLocatorName. The RouteMeasureLocator object is shown in the following illustration:
The RouteMeasureLocatorName object is shown in the following illustration:
The IRouteLocatorName interface is used to retrieve the properties of a RouteLocator object.
LocatorName is an abstract class that can be used to refer to a Locator object. RouteLocatorName is an abstract class that can be used to refer to a RouteLocator object. RouteMeasureLocatorName is a class that can be used to refer to a RouteMeasureLocator object. It is a specific implementation of LocatorName and RouteLocatorName.
All route locator name classes implement the IRouteLocatorName interface. This interface is used for setting and retrieving the properties of a RouteLocatorName object. The following are some things to note about this interface's properties:
  • RouteFeatureClassName—Polyline feature class with m-values.
  • RouteIDFieldName—Any numeric or text field containing the route identifiers. This field relates to a similar field in an event table.
  • RouteMeasureUnits—Units of the m-values stored in the routes. The default is esriUnknownUnits.
  • RouteWhereClause—String that limits the number of routes on which route locations can be found.
The following code example shows how to create RouteMeasureLocator via RouteMeasureLocatorName:
[C#]
IDataset dS=(IDataset)routeFC; // A polylineM feature class.
IName name=dS.FullName;
IRouteLocatorName rtLocatorName=new RouteMeasureLocatorNameClass();
rtLocatorName.RouteFeatureClassName=name;
rtLocatorName.RouteIDFieldName="rkey";
rtLocatorName.RouteMeasureUnit=esriUnits.esriMeters;
name=(IName)rtLocatorName;
IRouteLocator2 rtLocator=(IRouteLocator2)name.Open();
[VB.NET]
Dim dS As IDataset=CType(routeFC, IDataset) ' A polylineM feature class.
Dim Name As IName=dS.FullName
Dim rtLocatorName As IRouteLocatorName=New RouteMeasureLocatorNameClass()
rtLocatorName.RouteFeatureClassName=Name
rtLocatorName.RouteIDFieldName="rkey"
rtLocatorName.RouteMeasureUnit=esriUnits.esriMeters
Name=CType(rtLocatorName, IName)
Dim rtLocator As IRouteLocator2=CType(Name.Open(), IRouteLocator2)

RouteMeasureLocation, RouteMeasurePointLocation, and RouteMeasureLineLocation

RouteMeasureLocation describes a portion of a route or a single position along a route.
The IRouteLocation interface lets you define the properties of a route location. For example, route locations occur along a single route; therefore, set that value here. Additionally, you identify the units in which the route location was collected and specify whether you want the route location's shape offset from its route when it is located.
Offsets are in the spatial reference units of the route feature class (and not necessarily the same units as the route feature class' measures). Therefore, an offset on route data stored in geographic units might produce inconsistent results. Offsets are used for rendering purposes only.
Setting the IRouteLocation.MeasureUnit property enables you to perform on-the-fly measure conversion. This property corresponds to IRouteLocatorName.RouteMeasureUnit. For example, you might know the position of a route location in miles, but your route feature class has its measures stored in meters. By setting these values accordingly, you can achieve measure conversion.
RouteMeasureLineLocation is a class that describes portions of a route using from- and to-measure locations. The RouteMeasureLineLocation object is shown in the following illustration:
The IRouteMeasureLineLocation interface is where you set the route location's from- and to-measure values, for example, to find a location from 2,500 meters to 3,500 meters along route 10; and you want this location to be offset 25 meters from the route. See the following code example:
[C#]
IRouteLocation routeLoc=new RouteMeasureLineLocationClass();
routeLoc.MeasureUnit=esriUnits.esriMeters;
routeLoc.RouteID=10;
routeLoc.LateralOffset=25;
IRouteMeasureLineLocation rMLineLoc=(IRouteMeasureLineLocation)routeLoc;
rMLineLoc.FromMeasure=2500;
rMLineLoc.ToMeasure=3500;
[VB.NET]
Dim routeLoc As IRouteLocation=New RouteMeasureLineLocationClass()
routeLoc.MeasureUnit=esriUnits.esriMeters
routeLoc.RouteID=10
routeLoc.LateralOffset=25
Dim rMLineLoc As IRouteMeasureLineLocation=CType(routeLoc, IRouteMeasureLineLocation)
rMLineLoc.FromMeasure=2500
rMLineLoc.ToMeasure=3500
RouteMeasurePointLocation is a class that uses a single m-value to describe a single position along a route.
The IRouteMeasurePointLocation interface is where you set the route location's m-value. For example, to find a location 565.5 meters along route 10, your code would look like the following:
[C#]
IRouteLocation routeLocation=new RouteMeasurePointLocationClass();
routeLocation.MeasureUnit=esriUnits.esriMeters;
routeLocation.RouteID=10;
routeLocation.LateralOffset=0;
IRouteMeasurePointLocation rMPointLoc=(IRouteMeasurePointLocation)routeLocation;
rMPointLoc.Measure=565.5;
[VB.NET]
Dim routeLocation As IRouteLocation=New RouteMeasurePointLocationClass()
routeLocation.MeasureUnit=esriUnits.esriMeters
routeLocation.RouteID=10
routeLocation.LateralOffset=0
Dim rMPointLoc As IRouteMeasurePointLocation=CType(routeLocation, IRouteMeasurePointLocation)
rMPointLoc.Measure=565.5
Once you have created a route location, determine its geometry by calling the IRouteLocator.Locate method as shown in the following code example. (Refer to the previous code examples to see how the RouteLocator object was created.)
[C#]
IGeometry geom;
esriLocatingError locError;
rtLocator.Locate((IRouteLocation)rMPointLoc, out geom, out locError);
[VB.NET]
Dim geom As IGeometry
Dim locError As esriLocatingError
rtLocator.Locate(CType(rMPointLoc, IRouteLocation), geom, locError)

RouteEventProperties, RouteMeasurePointProperties, and RouteMeasureLineProperties

An event table is a table that stores route locations and associated attributes. An event is a row from an event table. For example, an event might be a speed limit of 110 km/h on route 50 from km 92 to 138. In this case, the route location information of route 50 between km 92 and 138 is used to reference an attribute to a particular portion of a route in a route feature class.
Create RouteEventProperties to identify certain characteristics of the table so that it can be recognized as an event table. RouteEventProperties are helper objects for RouteEventSource. The RouteEventProperties helper object is shown in the following illustration:
The IRouteEventProperties2 interface inherits from IRouteEventProperties and establishes the route key field, the type of measure units in which the events were collected, and (optionally) the lateral offset field.
The AddErrorField property indicates whether you want an additional field added to your RouteEventSource for storing event-locating errors. The route key (EventRouteIDFieldName) defined on this interface is related to the RouteIDFieldName property on both IRouteLocator and IRouteLocatorName. This is how events are located along their respective routes. EventRouteIDFieldName does not have to have the same name as RouteIDFieldName, but it must store similar data.
RouteMeasureLineProperties is a class used to specify the characteristics of a line event table.
The IRouteMeasureLineProperties interface is where you identify the line event table's from- and to-measure fields. Each line event's measures reflect the distance from the lowest measure along its route. To set up line event properties where your table has an offset field, your code would look like the following:
[C#]
IRouteEventProperties2 rtProp=new RouteMeasureLinePropertiesClass();
rtProp.AddErrorField=true;
rtProp.ErrorFieldName="LOC_ERROR";
rtProp.EventMeasureUnit=esriUnits.esriMeters;
rtProp.EventRouteIDFieldName="rkey";
rtProp.LateralOffsetFieldName="Offset";
IRouteMeasureLineProperties rMLineProp=(IRouteMeasureLineProperties)rtProp;
rMLineProp.FromMeasureFieldName="fmp";
rMLineProp.ToMeasureFieldName="tmp";
[VB.NET]
Dim rtProp As IRouteEventProperties2=New RouteMeasureLinePropertiesClass()
rtProp.AddErrorField=True
rtProp.ErrorFieldName="LOC_ERROR"
rtProp.EventMeasureUnit=esriUnits.esriMeters
rtProp.EventRouteIDFieldName="rkey"
rtProp.LateralOffsetFieldName="Offset"
Dim rMLineProp As IRouteMeasureLineProperties=CType(rtProp, IRouteMeasureLineProperties)
rMLineProp.FromMeasureFieldName="fmp"
rMLineProp.ToMeasureFieldName="tmp"
RouteMeasurePointProperties is a class used to specify the characteristics of a point event table.
The IRouteMeasurePointProperties2 interface inherits from IRouteMeasurePointProperties and is where you identify the point event table's measure field. Each point event's measure reflects the distance from the lowest measure along its route.
The AddAngleField method indicates whether you want a field added to RouteEventSource to store the angle of the route where the point event is placed. This is useful for things such as rotating marker symbols. The normal (perpendicular) or tangent angle can be calculated.
By default, when a point event is located along a route, a point feature is created. In some applications, however, route measures are not unique. The AsPointFeature property provides the ability to create multipoint features.
The following code example shows how to set up point event properties:
[C#]
IRouteEventProperties2 rtProp=new RouteMeasurePointPropertiesClass();
rtProp.AddErrorField=true;
rtProp.ErrorFieldName="LOC_ERROR";
rtProp.EventMeasureUnit=esriUnits.esriMeters;
rtProp.EventRouteIDFieldName="rKey";
IRouteMeasurePointProperties2 rMPointProp=(IRouteMeasurePointProperties2)rtProp;
rMPointProp.MeasureFieldName="mile";
rMPointProp.AddAngleField=true;
rMPointProp.AngleFieldName="LOC_ANGLE";
[VB.NET]
Dim rtProp As IRouteEventProperties2=New RouteMeasurePointPropertiesClass()
rtProp.AddErrorField=True
rtProp.ErrorFieldName="LOC_ERROR"
rtProp.EventMeasureUnit=esriUnits.esriMeters
rtProp.EventRouteIDFieldName="rKey"
Dim rMPointProp As IRouteMeasurePointProperties2=CType(rtProp, IRouteMeasurePointProperties2)
rMPointProp.MeasureFieldName="mile"
rMPointProp.AddAngleField=True
rMPointProp.AngleFieldName="LOC_ANGLE"

RouteEventSource and RouteEventSourceName

RouteEventSource serves an event table as a dynamic feature class. Every row in the table is served as a feature whose shape is calculated on the fly when it is requested. This is dynamic segmentation. The RouteEventSource object is shown in the following illustration:
To serve an event table as a feature class, RouteEventSource needs information, such as the event table, the RouteEventProperties, and the RouteLocator. The IRouteEventSource interface retrieves this information. As with the previously discussed locator objects, RouteEventSource is created via its name object counterpart, RouteEventSourceName. The RouteEventSourceName object is shown in the following illustration:
RouteEventSourceName specifies a RouteEventSource object and can be used to instantiate it.
The IRouteEventSourceName interface sets the event table, the RouteEventProperties, and the RouteLocator. The following code example shows how to create RouteEventSource via RouteEventSourceName. Here, RouteMeasurePointProperties and RouteLocatorName are already created. These values are set using IRouteEventSourceName.
[C#]
IDataset ds=(IDataset)eventTable;
IName name=ds.FullName;
IRouteEventSourceName rtEvtSrcName=new RouteEventSourceNameClass();
rtEvtSrcName.EventTableName=name;
rtEvtSrcName.EventProperties=(IRouteEventProperties)rmPtProp;
rtEvtSrcName.RouteLocatorName=rtLocatorName;
name=(IName)rtLocatorName;
IRouteEventSource rtEvtSrc=(IRouteEventSource)name.Open();
[VB.NET]
Dim ds As IDataset=CType(eventTable, IDataset)
Dim Name As IName=ds.FullName
Dim rtEvtSrcName As IRouteEventSourceName=New RouteEventSourceNameClass()
rtEvtSrcName.EventTableName=Name
rtEvtSrcName.EventProperties=CType(rmPtProp, IRouteEventProperties)
rtEvtSrcName.RouteLocatorName=rtLocatorName
Name=CType(rtLocatorName, IName)
Dim rtEvtSrc As IRouteEventSource=CType(Name.Open(), IRouteEventSource)
Because RouteEventSource is a subclass of a feature class, it can be used anywhere a feature class can. For example, RouteEventSource can act as the basis of a feature layer in ArcMap, and its attributes can be edited directly with the editing tools in ArcMap. There might be limitations imposed by the event table however. For example, you cannot directly edit a feature class created from a delimited text file table since the Editor does not allow text files to be edited directly.
In RouteEventSource, there is one feature for every row of the original event table. In some cases, however, the features have empty shapes. This is because there was a reason the event could not be properly located. Other times, an event can only be partially located (this happens for line events only).
The IEventSourceErrors interface exposes methods that allow you to determine the locating errors of events. The following code example uses IEventSourceErrors.GetErrors to create an enumeration of the event rows that did not locate properly:
[C#]
public void TestGetErrors(IRouteEventSource res)
{
    IRow row;
    esriLocatingError locError;
    IEventSourceErrors esErrors=(IEventSourceErrors)res;
    IEnumEventError eEnum=esErrors.GetErrors();
    eEnum.Next(out row, out locError);
    do
    {
        switch (locError)
        {
            case esriLocatingError.LOCATING_OK:
                Console.WriteLine(row.OID + ": No error");
                break;
            case esriLocatingError.LOCATING_E_INVALIDRID:
                Console.WriteLine(row.OID + ": Invalid route ID");
                break;
            case esriLocatingError.LOCATING_E_INVALIDMEASURE:
                Console.WriteLine(row.OID + ": Invalid measure");
                break;
            case esriLocatingError.LOCATING_E_CANT_FIND_ROUTE:
                Console.WriteLine(row.OID + ": Can't find route");
                break;
            case esriLocatingError.LOCATING_E_ROUTE_SHAPE_EMPTY:
                Console.WriteLine(row.OID + ": Route's shape is empty");
                break;
            case esriLocatingError.LOCATING_E_CANT_FIND_LOCATION:
                Console.WriteLine(row.OID + ": Can't find location along route");
                break;
            case esriLocatingError.LOCATING_E_CANT_FIND_EXTENT:
                Console.WriteLine(row.OID + ": Can't find extent along route");
                break;
            case esriLocatingError.LOCATING_E_FROM_PARTIAL_MATCH:
                Console.WriteLine(row.OID + ": From-measure partial match");
                break;
            case esriLocatingError.LOCATING_E_TO_PARTIAL_MATCH:
                Console.WriteLine(row.OID + ": To-measure partial match");
                break;
            case esriLocatingError.LOCATING_E_ROUTE_MS_NULL:
                Console.WriteLine(row.OID + ": Route's measures are not set");
                break;
            case esriLocatingError.LOCATING_E_ROUTE_NOT_MAWARE:
                Console.WriteLine(row.OID + 
                    ": Route is not capable of storing measures");
                break;
            case esriLocatingError.LOCATING_E_FROM_TO_PARTIAL_MATCH:
                Console.WriteLine(row.OID + 
                    ": From-measure and to-measure partial match");
                break;
            default:
                Console.WriteLine(row.OID + ": Non-typical locating error - " +
                    locError);
                break;
        }
        eEnum.Next(out row, out locError);
    }
    while (row != null);
}
[VB.NET]
Public Sub TestGetErrors(ByVal res As IRouteEventSource)
    Dim row As IRow
    Dim locError As esriLocatingError
    Dim esErrors As IEventSourceErrors=CType(res, IEventSourceErrors)
    Dim eEnum As IEnumEventError=esErrors.GetErrors()
    eEnum.Next(row, locError)
    Do
        Select Case locError
            Case esriLocatingError.LOCATING_OK
                Console.WriteLine(row.OID & ": No error")
            Case esriLocatingError.LOCATING_E_INVALIDRID
                Console.WriteLine(row.OID & ": Invalid route ID")
            Case esriLocatingError.LOCATING_E_INVALIDMEASURE
                Console.WriteLine(row.OID & ": Invalid measure")
            Case esriLocatingError.LOCATING_E_CANT_FIND_ROUTE
                Console.WriteLine(row.OID & ": Can't find route")
            Case esriLocatingError.LOCATING_E_ROUTE_SHAPE_EMPTY
                Console.WriteLine(row.OID & ": Route's shape is empty")
            Case esriLocatingError.LOCATING_E_CANT_FIND_LOCATION
                Console.WriteLine(row.OID & ": Can't find location along route")
            Case esriLocatingError.LOCATING_E_CANT_FIND_EXTENT
                Console.WriteLine(row.OID & ": Can't find extent along route")
            Case esriLocatingError.LOCATING_E_FROM_PARTIAL_MATCH
                Console.WriteLine(row.OID & ": From-measure partial match")
            Case esriLocatingError.LOCATING_E_TO_PARTIAL_MATCH
                Console.WriteLine(row.OID & ": To-measure partial match")
            Case esriLocatingError.LOCATING_E_ROUTE_MS_NULL
                Console.WriteLine(row.OID & ": Route's measures are not set")
            Case esriLocatingError.LOCATING_E_ROUTE_NOT_MAWARE
                Console.WriteLine(row.OID & ": Route is not capable of storing measures")
            Case esriLocatingError.LOCATING_E_FROM_TO_PARTIAL_MATCH
                Console.WriteLine(row.OID & ": From-measure and to-measure partial match")
            Case Else
                Console.WriteLine(row.OID & ": Non-typical locating error - " & locError)
        End Select
        eEnum.Next(row, locError)
    Loop While Not row Is Nothing
End Sub
The RouteLayerExtension class is a helper class for dynamic segmentation in the ArcMap user interface.
Every feature layer has zero or one RouteLayerExtension object associated with it. When a new feature layer is added in ArcMap, its associated feature class' geometry definition is inspected to determine if IGeometryDef.GeometryType=esriGeometryPolyLine and IGeometryDef.HasM=true. If both of these criteria are met (and the layer is not based on linear route events), RouteLayerExtension is attached to the layer. If you are creating feature layers that will not be added to an ArcMap document, you are responsible for attaching RouteLayerExtension if you want to use its services later in your application.
The IRouteLayerExtension interface is where you set the route identifier field name for the route feature class. The other methods on this interface are used by the Add Route Events and the Identify Route Locations dialog boxes to automatically populate certain parameters.

Route and event geoprocessing in linear referencing

Before starting a linear referencing project, you need route data. This data might not exist, might exist but without the appropriate measure system defined, or might exist in a format you do not want to use.
A route is any linear feature that has a unique identifier and measurement system stored with it. Routes can be stored in coverages; shapefiles; and personal, file, and ArcSDE geodatabases.
The following illustration shows the route and event geoprocessing objects:
A RouteMeasureCreator object is used to create routes that are stored in a shapefile or in a personal, file, or ArcSDE geodatabase. RouteMeasureCreator merges linear features that share a common identifier and sets the measure values.
The IRouteMeasureCreator2 interface inherits from IRouteMeasureCreator. It exposes the methods and properties necessary for creating route feature classes. You can specify either InputFeatureClass or InputFeatureSelection property. The latter allows you to create routes from only a portion of the features in your input feature class. The input line features are merged on InputRouteIDFieldName.

Measure values are not known

The CreateUsingCoordinatePriority2 method is used when the route measures on the input line features are not known. This method generates the measures by accumulating the digitized length or by accumulating a numeric attribute value of the input features. If you use digitized length, the units of the output route measures will be the same as the coordinate system of the output geometry definition's spatial reference (feet, meters, and so on). To use the digitized length, pass an empty string for the lengthFieldName parameter.
With CreateUsingCoordinatePriority2, you control the direction measures are assigned to the routes by specifying the coordinate priority of the starting measure. The coordinate priority can be set using the values in the esriMSeedingCorner enumeration—esriMSeedingUpperLeft, esriMSeedingBottomLeft, esriMSeedingUpperRight, or esriMSeedingBottomRight. These options are determined by placing the minimum-bounding rectangle around the input features that are going to be merged to create one route. In the following illustration, the starting priority is lower left:
Routes with multiple, disjointed parts are supported. A route representing a road, for example, might have the same name on either side of a river. For situations like this, you can ignore spatial gaps between parts when using CreateUsingCoordinatePriority2. If you ignore spatial gaps, route measures will be continuous when a disjointed route is created. If you want the spatial gap incorporated in the measures, the gap distance is the straight-line distance between the endpoints of the parts. The units of the gap will be that of the coordinate system of the output geometry definition's spatial reference, which might or might not be the same as the measure units. These scenarios are shown in the following illustration:
 

Measure values are known

The CreateUsing2Fields2 method is used when measure values already exist as attributes of the input linear features. That is, two attributes exist that represent from- and to-measure information for the input lines. When using this method, it is important to orient each input linear feature in the direction of increasing measure to prevent routes that have measures that do not always increase. In the following illustration, measure values are obtained by using the values in the FR_M and TO_M fields. The digitized direction of the input features determines the direction of the output route.

Calibrating route measures

When route measures are inaccurate, events will not be located properly. It is possible to adjust route measures to correspond with known measure locations using RouteMeasureCalibrator. RouteMeasureCalibrator adjusts route measures by reading measure information stored as an attribute in a point feature class. Each point falls on the particular route it calibrates or within a given tolerance. Many points can be used to calibrate a single route.
During the calibration process, a new vertex is created where the calibration points intersect the route. The measure value on these new vertices corresponds to the measure value stored as a point attribute. The measure values on other preexisting route vertices can be interpolated or extrapolated.
The calibration process creates a vertex for every point within the specified tolerance. The measure at each new vertex corresponds to the point's measure value as shown in the following illustration. You control whether the remaining vertices will have their measure interpolated or extrapolated.
The IRouteMeasureCalibrator2 interface inherits from IRouteMeasureCalibrator. It exposes the methods and properties necessary for calibrating routes. You can specify either InputFeatureClass or InputFeatureSelection. The latter allows you to limit the number of points used to calibrate. The RouteLocator property should not be used. Specifying RouteFeatureClass and RouteIDFieldName, or RouteFeatureSelection and RouteIDFieldName is the usage for this interface. RouteFeatureSelection allows you to limit the routes that will be considered for calibration.
For the measure value on a vertex to be interpolated or extrapolated, a calibration ratio is needed. There are two ways this ratio can be determined. The CalibrateRoutesByDistance method uses the shortest path distance between the input points as shown in the following illustration:
The CalibrateRoutesByMs method uses the existing measure distance between the input points as shown in the following illustration. This method is useful when the length to measure ratio on the input route is not consistent, and you are using the calibration process to fine-tune a route's measures.
Both whole and partial routes can be calibrated. You can interpolate between the input points, extrapolate before the input points, extrapolate after the input points, or use any combination of these three methods. The updateHow parameter is given as a combination of esriGeometryUpdateMEnum values.
When combining multiple values, the bitwise OR operator should always be used. This assures an error-free combination of the values (as long as the attempted combination is valid). Do not use the addition operator (+) to combine the values, as unexpected results can occur. For example, to interpolate between the input points and to extrapolate before and after the input points, you would use 7, which equates to: esriGeometryInterpolate OR esriGeometryExtrapolateBefore OR esriGeometryExtrapolateAfter. A value of 0 only splits the input polyline and assigns the Ms value to the recently created vertex.
When calibrating disjointed routes, you can ignore the spatial gap between the parts. If you ignore spatial gaps, route measures will be continuous. If you want the spatial gap incorporated in the measures, the gap distance is the straight-line distance between the endpoints of the parts. The units of the gap will be that of the coordinate system of the output geometry definition’s spatial reference, which might or might not be the same as the measure units. Ignoring spatial gaps is only valid when using CalibrateRoutesByDistance.
Event processing operations include dissolving events, concatenating events, and event overlay (line on line and point on line). Event geoprocessing is exposed via the RouteMeasureGeoprocessor class.
The IRouteMeasureEventGeoprocessor2 interface inherits from IRouteMeasureEventGeoprocessor and provides access to the event geoprocessing operations.

Concatenating and dissolving events

Event dissolving and event concatenating involve combining records in line event tables if they are on the same route and have the same value for specified fields. The results are written to a new line event table. The difference between dissolving and concatenating is that concatenating only combines events in situations where the to-measure of one event matches the from-measure of the next event. Dissolving events combines events when there is measure overlap. An example of concatenate and dissolve is shown in the following illustration:

Line-on-line overlay

Line-on-line overlay involves the overlay of two line event tables to produce a single line event table. For example, you can take an event table that describes pavement cracking and overlay it with pavement resurfacing dates. The results of such an overlay can be used to find the characteristics of the oldest paved sections.
When performing a line-on-line overlay, the results can contain events that have no length (for example, the from- and to-measure values are the same). The IRouteMeasureEventGeoprocessor.KeepZeroLengthLineEvents property can be used to indicate whether you want such events in your result set. An example of line-on-line overlay is shown in the following illustration:

Line-on-point overlay

Line-on-point overlay involves the overlay of a point event table with a line event table to produce a single point or line event table. The intersection of a point and a line event table produces a point event table. The union of a point and a line event table produces a line event table. An example of line-on-point overlay is shown in the following illustration:

Point-on-point overlay

An example of point-on-point overlay is shown in the following illustration:

Locating features along routes

The RouteLocatorOperations class computes the intersection of input features (point, line, or polygon) and route features, and writes the route and measure information to a new event table. The new table can be dBASE or a personal, file, or ArcSDE geodatabase.
The IRouteLocatorOperations2 interface inherits from IRouteLocatorOperations and provides access to the methods that allow features to be located along routes. You can specify InputFeatureClass or InputFeatureSelection. The latter allows you to limit the number of features to be located. The RouteLocator property should not be used. Specifying RouteFeatureClass and RouteIDFieldName, or RouteFeatureSelection and RouteIDFieldName is the usage of this interface. RouteFeatureSelection allows you to limit the routes that will be considered for locating.
  • Line features—The LocateLineFeatures method determines the route and measure information where the lines intersect with routes. This intersection is based on a specified cluster tolerance that represents the maximum tolerated distance between the input lines and the target routes. This method should not be thought of as a conflation tool, and large cluster tolerances should be avoided. Always use the smallest cluster tolerance possible to achieve the best results. When using this method, outputProperties should be for line events (RouteMeasureLineProperties).
Locating line features along routes creates a line event table as shown in the following illustration:

  • Point features—The LocatePointFeatures method determines the route and measure information where the points intersect with routes. This intersection is based on a search radius, which defines how far around each point a search will be done to find a target route. When using this method, outputProperties should be for point events (RouteMeasurePointProperties).

    The LocatePointEvents method represents a variation of the LocatePointFeatures algorithm and works specifically on point events. When using this method, outputProperties should be for point events (RouteMeasurePointProperties).

    Locating points along routes creates a point event table containing the route identifier and measure information of where points intersect routes as shown in the following illustration:

  • Polygon features—The LocatePolygonFeatures method determines the route and measure information where the polygons intersect with routes. When using this method, outputProperties should be for line events (RouteMeasureLineProperties).

    Once polygon data has been located along routes, the resulting event table can be used to calculate the length of route that traveled through each polygon as shown in the following illustration:


Hatching in linear referencing

Hatching is a type of labeling that posts and labels hatch marks or symbols at a regular interval along measured linear features. Hatching can be used for both distance-based and non distance-based measures. Distance-based measures include kilometers, miles, feet, and meters. Non distance-based measures include seismic shot point numbers where measure values generally increase in even intervals based on some nominal distance.
The following illustration shows the hatching objects:
Hatching is exposed via the HatchLayerExtension class. Every feature layer has zero or one HatchLayerExtension objects associated with it. Whenever a new feature layer is added in ArcMap, its associated feature class' geometry definition is inspected to see whether IGeometryDef.GeometryType=esriGeometryPolyLine and IGeometryDef.HasM=true (this includes RouteEventSource objects based on linear event tables). If both of these criteria are met, HatchLayerExtension is attached to the layer. When creating feature layers that will not be added to an ArcMap document, you are responsible for attaching HatchLayerExtension.
The IHatchLayerExtension interface is where you control the properties of HatchLayerExtension. To display hatches on a layer, there must be at least one HatchClass associated with HatchLayerExtension. HatchLayerExtension has one HatchClass associated with it when it is created. You can manipulate the properties of HatchClass, or you can remove it (RemoveClass or RemoveAll) and add another (AddClass).
The following code example shows how to attach HatchLayerExtension to a new feature layer:
[C#]
IWorkspaceFactory workspaceFactory=new ShapefileWorkspaceFactoryClass();
IFeatureWorkspace featureWorkspace=(IFeatureWorkspace)
    workspaceFactory.OpenFromFile("d:\\data\\dyndata", 0);
IFeatureLayer featureLayer=new FeatureLayerClass();
featureLayer.FeatureClass=featureWorkspace.OpenFeatureClass("roads_hwy");
featureLayer.Name=featureLayer.FeatureClass.AliasName;
ILayerExtensions layerExt=(ILayerExtensions)featureLayer;
IHatchLayerExtension hatchExt=new HatchLayerExtensionClass();
hatchExt.AddClass("Class1", hatchClass); //hatchClass is created elsewhere.
layerExt.AddExtension(hatchExt);
[VB.NET]
Dim workspaceFactory As IWorkspaceFactory=New ShapefileWorkspaceFactoryClass()
Dim featureWorkspace As IFeatureWorkspace=CType(workspaceFactory.OpenFromFile("d:\data\dyndata", 0), IFeatureWorkspace)
Dim featureLayer As IFeatureLayer=New FeatureLayerClass()
featureLayer.FeatureClass=featureWorkspace.OpenFeatureClass("roads_hwy")
featureLayer.Name=featureLayer.FeatureClass.AliasName
Dim layerExt As ILayerExtensions=CType(featureLayer, ILayerExtensions)
Dim hatchExt As IHatchLayerExtension=New HatchLayerExtensionClass()
hatchExt.AddClass("Class1", hatchClass) 'hatchClass is created elsewhere.
layerExt.AddExtension(hatchExt)
The reason HatchLayerExtension can have more than one HatchClass is because it's useful to hatch different features in different ways (IHatchClass.Filter). For example, you can hatch only major highways and leave local roads and collector roads unhatched. It is also useful to hatch the same features in different ways, depending on the map's scale (IHatchClass.MaximumScale and IHatchClass.MinimumScale). For example, you can leave features unhatched when zoomed out to full extent but have them hatched when zoomed in to a certain scale threshold.
HatchClass has a HatchTemplate. Each HatchTemplate contains one or more HatchDefinition objects. HatchDefinition defines the properties of the hatches. There are two HatchDefinition objects—HatchMarkerDefinition and HatchLineDefinition. The following illustration shows the HatchClass and HatchTemplate objects:

Hatching concepts

The easiest way to understand the HatchClass and HatchDefinition classes is to use a ruler analogy. On a ruler, there is a series of vertical lines or hatches, separated by a regular interval. For example, on a centimeter (cm) ruler, the hatches are typically spaced every millimeter (mm). One millimeter is 1/10 of a centimeter; therefore, the hatch interval is 0.1.
Not all hatches on a ruler are the same. Some are longer than others and some have text, while others do not. On a centimeter ruler, the hatches placed at every millimeter (0.1 cm) are the shortest. The hatches placed at every 5 millimeters (0.5 cm) are a bit longer. The hatches placed every 10 millimeters (1 cm) are the longest. The longest hatches typically have text to indicate the measure value.
In the following illustration, the ruler is a HatchClass. It is a container for three HatchDefinition objects. Each HatchDefinition is placed at a multiple of the hatch interval (IHatchClass.HatchInterval). The longest hatches are placed at every 0.1 x 10 measure units. The second longest hatches are placed at every 0.1 x 5 measure units. The shortest hatches are placed at every 0.1 x 1 measure units. When placed on a map, hatch definitions within a single hatch class will not draw on top of one another.
Suppose you have linear features whose measurements are in miles. You want to place a hatch every quarter mile. This is a hatch interval of 0.25. Further, you want the hatches placed at every 0.25, 0.5, and 1 mile to look different (different length, different color). You want the hatches every mile to have text. Lastly, you want hatches at the ends of the feature. These end hatches are to look different than all other hatches and will have text. The following illustration shows conceptually how hatching works in this scenario:
 

Hatching options

There are many ways to control how hatches appear on a map. Use the methods on the IHatchClass, IHatchTemplate, IHatchDefinition, and IHatchLineDefinition interfaces. The following illustrations show some of the ways hatches can be manipulated.
The IHatchClass interface is where you set properties that are specific to the data being hatched. As such, many of the properties on this interface allow data to be derived from a field or can be a specific value (see the following discussion on the HatchInputValue class). The most important property on IHatchClass is HatchInterval. This specifies, in measure units, where the hatches appear on the line; however, hatches can also be placed only at the route ends. In this case, do not specify a hatch interval.
All of the hatch definitions in a hatch class can be offset by the same amount using the LateralOffset property as shown in the following illustration. Offsets are specified in the units of the line feature class' coordinate system (for example, feet or meters) or by the settings IHatchTemplate.DisplayUnits and IHatchTemplate.ConvertUnits. However, each individual hatch definition can also have its own offset (IHatchDefinition.Offset.
Hatching can start at a measure location other than the low measure (StartRange) of a route. Further, hatching can finish at a location other than the high measure (EndRange) of a route. This scenario is shown in the following illustration:
Before giving a code example for HatchClass, it is important to mention the HatchInputValue class. HatchInputValue is a helper class for many of the methods on the IHatchClass interface. For many hatching properties, you might want to retrieve values from one of the feature’s attributes, or you might want to use predefined values. The following code example shows how to create a HatchClass and set some of its more important properties:
[C#]
IHatchClass hatchClass=new HatchClassClass();
IHatchInputValue hatchInput1=new HatchInputValueClass();
hatchInput1.Value=100;
hatchClass.HatchInterval=hatchInput1;
IHatchInputValue hatchInput2=new HatchInputValueClass();
hatchInput2.Field="MMIN";
hatchClass.StartRange=hatchInput2;
IHatchInputValue hatchInput3=new HatchInputValueClass();
hatchInput3.Field="MMAX";
hatchClass.EndRange=hatchInput3;
IHatchInputValue hatchInput4=new HatchInputValueClass();
hatchInput4.Value=50;
hatchClass.LateralOffset=hatchInput4;
[VB.NET]
Dim hatchClass As IHatchClass=New HatchClassClass()
Dim hatchInput1 As IHatchInputValue=New HatchInputValueClass()
hatchInput1.Value=100
hatchClass.HatchInterval=hatchInput1
Dim hatchInput2 As IHatchInputValue=New HatchInputValueClass()
hatchInput2.Field="MMIN"
hatchClass.StartRange=hatchInput2
Dim hatchInput3 As IHatchInputValue=New HatchInputValueClass()
hatchInput3.Field="MMAX"
hatchClass.EndRange=hatchInput3
Dim hatchInput4 As IHatchInputValue=New HatchInputValueClass()
hatchInput4.Value=50
hatchClass.LateralOffset=hatchInput4
Hatching templates can be stored in esriLocationUI.HatchStyleGalleryClass. Because the properties on IHatchClass can be data specific, they are not saved when you add items to HatchStyleGalleryClass.
The IHatchTemplate interface is where you set properties that are generic to all hatches in a HatchClass. These properties are not data specific. As such, these are the properties that get saved when you store hatches in HatchStyleGalleryClass. When a feature’s geometry has multiple parts, it's possible to apply hatches to the feature as a whole or to each part individually by setting HatchByPart to true as shown in the following illustration:
By default, the placement of hatches is adjusted to the hatch interval. This means that when a line’s low measure is not divisible by the interval, the first hatch will be placed at the first measure value that is divisible by the hatch interval. For example, a line whose measures range from 1.1 to 5.2 will have its first hatch placed at 1.25 when the hatch interval is 0.25. This behavior can be turned off by setting StartAtIntervalMultiple to false. End hatch definitions are not affected by this property and are not shown in the following illustration:
When a feature’s high measure is not divisible by the hatch interval and an end hatch definition has been defined, you can get two hatches that are very close to or on top of one another. To avoid this, specify an EndHatchDrawingTolerance, which informs the hatching algorithm to not place hatches when they fall within the tolerance of the end hatch as shown in the following illustration. The end hatch tolerance is specified in route measure units and its value is typically set to a value that is less than the hatch interval.
As previously discussed, HatchDefinition defines the properties of the hatches. There are two kinds of HatchDefinition—HatchMarkerDefinition and HatchLineDefinition.
TheIHatchDefinition interface is where you specify the properties that are common to both marker and line hatches.
As shown in the following illustration, each hatch definition can be displayed to the left, centered on, or to the right of a feature by setting the Alignment property. When set to center, hatches will be labeled to the left of the feature.
Hatch text is flipped as the direction of the feature changes, which makes the text more readable as shown in the following illustration. This can be turned off using AdjustTextOrientation, which orients the text in the direction of increasing measure.
A hatch definition will be labeled with the measure value when the TextSymbol property has been set. The number of decimal places is controlled by the DisplayPrecision property. You can add a prefix or a suffix to the measure value. The following code example adds the suffix "km" to every hatch in the HatchDefinition. Pay particular attention to the TextDisplay and Suffix properties.
[C#]
IHatchDefinition hatchDef=new HatchLineDefinitionClass();
hatchDef.HatchSymbol=(ISymbol)lineSymbol; //lineSymbol is created elsewhere.
hatchDef.TextDisplay=esriHatchTextDisplay.esriHatchTDPrefixSuffix;
hatchDef.TextSymbol=txtSymbol; //txtSymbol is created elsewhere.
hatchDef.Suffix="km";
hatchDef.DisplayPrecision=0;
[VB.NET]
Dim hatchDef As IHatchDefinition=New HatchLineDefinitionClass()
hatchDef.HatchSymbol=CType(lineSymbol, ISymbol) 'lineSymbol is created elsewhere.
hatchDef.TextDisplay=esriHatchTextDisplay.esriHatchTDPrefixSuffix
hatchDef.TextSymbol=txtSymbol 'txtSymbol is created elsewhere.
hatchDef.Suffix="km"
hatchDef.DisplayPrecision=0
For certain applications, adding a prefix or suffix is not enough. For these applications, you can use VBScript or JScript to specify a function that will be evaluated. This function must be called FindLabel and at the very least, the esri__measure value must be passed in (there are two underscore characters in esri__measure). Fields from the layer being hatched can also be passed to the FindLabel function and are enclosed in square brackets [], regardless of the data type. In the following code example, the FindLabel function sets a hatch’s label to be an empty string when the measure value passed in (esri__measure) is within 25 measure units of the value stored in the MMax field. Otherwise, it appends the km suffix to the measure value. Pay particular attention to the TextDisplay, ExpressionParserEngine, ExpressionSimple, and Expression properties.
[C#]
IHatchDefinition hatchDef=new HatchLineDefinitionClass();
hatchDef.HatchSymbol=(ISymbol)lineSymbol; //lineSymbol is created elsewhere.
hatchDef.TextSymbol=txtSymbol; //txtSymbol is created elsewhere.
hatchDef.TextDisplay=esriHatchTextDisplay.esriHatchTDExpression;
hatchDef.ExpressionParserEngine=esriHatchExpressionEngine.esriHatchVBScriptEngine;
hatchDef.ExpressionSimple=false;
hatchDef.Expression="Function FindLabel (esri__measure, [MMax])" +
    Environment.NewLine + "mmax=[MMax]" + Environment.NewLine + 
    "m=esri__measure" + Environment.NewLine + "If (mmax - m) <= 25 Then " +
    Environment.NewLine + "FindLabel=" + @"""""" + Environment.NewLine + "Else" +
    Environment.NewLine + "FindLabel=CStr(Round(m, 1)) & " + @"""km""" +
    Environment.NewLine + "End If" + Environment.NewLine + "End Function";
[VB.NET]
Dim hatchDef As IHatchDefinition=New HatchLineDefinitionClass()
hatchDef.HatchSymbol=CType(lineSymbol, ISymbol) 'lineSymbol is created elsewhere.
hatchDef.TextSymbol=txtSymbol 'txtSymbol is created elsewhere.
hatchDef.TextDisplay=esriHatchTextDisplay.esriHatchTDExpression
hatchDef.ExpressionParserEngine=esriHatchExpressionEngine.esriHatchVBScriptEngine
hatchDef.ExpressionSimple=False
hatchDef.Expression="Function FindLabel (esri__measure, [MMax])" & Environment.NewLine & _
                      "mmax=[MMax]" & Environment.NewLine & "m=esri__measure" & Environment.NewLine & _
                      "If (mmax - m) <= 25 Then " & Environment.NewLine & "FindLabel=" & """""" & Environment.NewLine & _
                      "Else" & Environment.NewLine & "FindLabel=CStr(Round(m, 1)) & " & """km""" & Environment.NewLine & _
                      "End If" & Environment.NewLine & "End Function"
The FindLabel function in the previous code example looks like the following code example without the VBScript formatting:
[VBScript]Function FindLabel (esri__measure, [MMax]) mmax=[MMax] m=esri__measure If (mmax - m) <= 25 Then FindLabel="" Else FindLabel=CStr(Round(m, 1)) & " km" End If End Function
Each HatchTemplate can have an EndHatchDefinition. An end hatch definition pays no attention to the specified hatch interval. Rather, it draws hatch marks at the low- and high-measure of a linear feature. For cases in which hatches are placed too close together near the end of a feature, you can specify an EndHatchDrawingTolerance, which prevents certain hatches from drawing if they are within the tolerance (specified in measure units) of an end hatch. A HatchTemplate can only have one end hatch definition.
The IHatchLineDefinition interface is where you specify the properties that are specific to line hatches. At the very least, you should always set the Length property when dealing with line hatches. Length is specified in the units of the coordinate system of the route feature class' spatial reference or by the settings IHatchTemplate.DisplayUnits and IHatchTemplate.ConvertUnits.
By default, line hatches are drawn perpendicular to the feature. You can specify a SupplementalAngle that gets added to the calculated angle as shown in the following illustration: