Location


This document was published with and applies to ArcGIS 9.3.
A 10 version also exists. A 9.2 version also exists.
Supported with:
Library dependencies: System, SystemUI, Geometry, Display, Server, Output, Geodatabase, GISClient, ArcWeb, 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.
 
 

The objects that implement this functionality are grouped into a number of library subsystems. These library subsystems are:

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 either 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 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 either locators and templates for creating new locators or locator styles. The LocatorWorkspace abstract class allows you to create, store, and retrieve its locators and locator styles. The following diagram details the locator workspace objects.
 
 

For further information see:

How to open locator workspaces
Working with locators and locator styles

Locator styles in geocoding

A locator style is a template for creating a new locator. ArcGIS includes the implementation of the LocatorStyle abstract class on which to base new address locators. The ESRIFDOAddressLocatorStyle class is a template for creating locators that use Feature-Data-Object (FDO) reference data, such as shapefiles or geodatabase feature classes. The following diagram details the locator style objects.
 
 
Each locator style class supports the interfaces required to specify the reference data and geocoding options for a locator object. To create a new locator, a developer retrieves the locator style on which the new locator will be 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's 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:

How to create address locators
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 ESRIFDOAddressLocator. The following diagram details the reference data objects.
 
 
 
Each ESRIFDOAddressLocator 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 FDO data sources that ESRIFDOAddressLocator uses. The Name property on the IReferenceDataTable interface returns a reference to a TableName object that represents the FDO data source used as reference data by the locator. Each ReferenceDataTable has a ReferenceDataIndexEnumerator, which specifies the geocoding indexes that an ESRIFDOAddressLocator 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 FDO data source that contain address information.
 
See How to create address locators - ESRIFDOAddressLocator for an example illustrating how to use the locator workspace, locator style, and locator reference data objects to create a new ESRIFDOAddressLocator.

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 diagram details 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.
 
IAdvancedGeocoding2 provides access to additional geocoding functionality. IAdvancedGeocoding2 can be used to standardize addresses, modify the address locator's settings, and rematch the geocoded feature class if you are not satisfied with the results after geocoding a table of addresses.

For further information see:

What is an address locator?
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: Address inspector (reverse geocoding)
Sample: Match an address by clicking a map

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 Server developers to create server applications that include geocoding functionality. ArcGIS Desktop and ArcGIS Engine developers may, however, use GeocodeServer objects to include geocoding functionality in ArcGIS 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 runtime based on event tables for which distance measures are available. A calibrated linear feature, or route, is a polyline feature that has m- (measure) values 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 don't have to have the same name).
 
This dynamic segmentation concept is shown in the following graphic.
 
 
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.
 
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 tables show some sample error code.
 
 
The following diagram details 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 diagram:
 
 
Route locations describe either 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 you're 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. The following code shows this.

[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 diagram:
 
 
The RouteMeasureLocatorName object is shown in the following diagram:
 
 
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. Some things to note about this interface's properties:
 
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, so you 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 may 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 diagram.
 
 
The IRouteMeasureLineLocation interface is where you set the route location’s from- and to-measure values. For example, suppose you wanted to find a location from 2,500 meters to 3,500 meters along route 10. Furthermore, you want this location to be offset 25 meters from the route. Your code would look like this:

[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, if you wanted to find a location 565.5 meters along route 10, your code would look like this:

[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've 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 may 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.
 
You need to 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 diagram.
 
 
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 this: 

[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 every time it is requested. This is dynamic segmentation. The RouteEventSource object is shown in the following diagram.
 
 
To serve an event table as a feature class, RouteEventSource needs to know things such as the event table, the RouteEventProperties, and the RouteLocator. The IRouteEventSource interface retrieves this information. As with the locator objects outlined earlier, RouteEventSource is created via its name object counterpart, RouteEventSourceName. The RouteEventSourceName object is shown in the following diagram.
 
 
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 may be some 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 some 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 some 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. 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. 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, however, you are responsible for attaching RouteLayerExtension if you want to make use of 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 dialogs to automatically populate certain parameters.

Route and event geoprocessing in linear referencing

Before starting a linear referencing project, you need route data. This data may not yet exist, may exist but without the appropriate measure system defined, or may exist in a format you don't 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 details 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 will generate the measures either 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, simply 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 example, 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 may or may not be the same as the measure units. These scenarios are shown in the following example.
 
 
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 example, 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 may 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 and/or extrapolated.
 
The calibration process creates a new vertex for every point within the specified tolerance. The measure at each new vertex will correspond to the point's measure value as shown in the following example. 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 recommended usage of 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 example.
 
 
The CalibrateRoutesByMs method uses the existing measure distance between the input points as shown in the following example. 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 may 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 will only split the input polyline and assign 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 may or may 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 both 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 will combine events when there is measure overlap. An example of concatenate and dissolve is shown in the following diagram.
 
 
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 could be used to find the characteristics of the oldest paved sections.
 
When performing a line-on-line overlay, the results may 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 diagram.
 
 
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 diagram.
 
 
Point-on-point overlay
An example of point-on-point overlay is shown in the following diagram.
 
 
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 either 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 recommended usage of this interface. RouteFeatureSelection allows you to limit the routes that will be considered for locating.
 
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 example.
 
 
 
 
 
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 is designed specifically to work on point events. When using this method, outputProperties should be for point events (RouteMeasurePointProperties).
 
Locating points along routes creates a new point event table containing the route identifier and measure information of where points intersect routes as shown in the following example.
 
 
 
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, as in the following example, to calculate the length of route that traveled through each polygon.
 
 

Hatching in linear referencing

Hatching is a type of labeling that is designed to post and label 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 diagram details 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. If you are creating feature layers that will not be added to an ArcMap document, however, 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 demonstrates 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 is 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 full extent but have them hatched when zoomed in to a certain scale threshold.
 
HatchClass has a HatchTemplate. Each HatchTemplate is composed of one or more HatchDefinition objects. HatchDefinition defines the properties of the hatches themselves. 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, so the hatch interval is 0.1.
 
Not all the hatches on a ruler are the same. Some are longer than others. Further, 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 example, 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 measures 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 example demonstrates conceptually how hatching works in this scenario.
 
 
Hatching options
There are many ways to control how hatches appear on a map. To do this, you will use the methods on the IHatchClass, IHatchTemplate, IHatchDefinition, and IHatchLineDefinition interfaces. The following graphics illustrate 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 discussion on the HatchInputValue class below). The most important property on IHatchClass is HatchInterval. This specifies, in measure units, where the hatches will appear on the line. Note, however, that hatches can also be placed only at the route ends. In this case, simply 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 example. Offsets are specified in the units of the line feature class’s coordinate system (e.g., feet or meters) or by the settings IHatchTemplate.DisplayUnits and IHatchTemplate.ConvertUnits. Note, however, that 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 example.
 
 
Before giving a code example for HatchClass, it is important to talk about the HatchInputValue class. HatchInputValue is a helper class for many of the methods on the IHatchClass interface. For many hatching properties, you may want to retrieve values from one of the feature’s attributes, or you may want to use predefined values. The following code shows how to create a new 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, however, 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 do get saved when you store hatches in HatchStyleGalleryClass. In the case where a feature’s geometry has multiple parts, it is possible to apply hatches to the feature as a whole or to each part individually, as shown in the following example, by setting HatchByPart to TRUE.
 
 
By default, the placement of hatches is adjusted to the hatch interval. This means that in cases where 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, however, by setting StartAtIntervalMultiple to FALSE. End hatch definitions are not affected by this property and are not shown in the following example.
 
 
In cases where 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, you can 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 example. 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 noted previously, HatchDefinition defines the properties of the hatches themselves. 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 example, 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. This is to make the text more readable as shown in the following example. This can be turned off using AdjustTextOrientation, so the text is always oriented 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, however, add a prefix and/or a suffix to the measure value. The following code 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, however, adding a prefix and/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 (note that 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.
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's 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 example.