Select to view content in your preferred language

Export only dominant routes from LRS?

3109
12
Jump to solution
01-28-2019 09:24 AM
AndrewVitale3
New Contributor III

I'm looking to export the entirety of our enterprise LRS out of ArcMap, but with a caveat. When there is a concurrency, I would like only the dominant route present in the exported data.

I've got a workflow that seems to give me what I want, but it's a lot of steps.

Here's what I'm currently using:

  1. Run the Calculate Route Concurrencies GP tool with the appropriate temporal view date
  2. Export all non-retired routes into a separate LRS feature class
  3. Select the dominant routes from the output table, and run the Make Route Event GP tool from the Location Referencing toolbox to create an event of dominant routes
  4. Use the Erase GP tool from the Analysis toolbox to remove all concurrent sections from the LRS feature class
  5. Merge the dominant events with the LRS feature class that's output from Erase
  6. Dissolve on Route ID

Does anyone have a simpler workflow to accomplish this? Do you see any flaws in this current workflow?

Thanks in advance.

Edit (1/30/2018):

I'm including a Python script that implements something similar to Amit Hazra‌'s model builder screenshot. Maybe someone will find it useful. Thanks for the tips, RHUG.

Edit (3/17/2020)

I ended up turning this into an ArcGIS Desktop Geoprocessing Tool as well. It's only been tested on ArcGIS Desktop 10.5.1 with an LRSN Route Network stored in a file geodatabase or SQL Server 2016 geodatabase. If you're using a different RDBMS, the where_clause that applies the Temporal View Date may be different.

GitHub - vitale232/ExportDominantNetwork: Export dominant routes from an Esri R&H ALRS. 

I've also attached a .zip directory containing the tool as of today's date.

import datetime
import os

import arcpy


def make_dominant_network(input_routes, concurrency_table, overwrite_flag=False):
    """
    After creating gaps in the input network where concurrencies exist,
    the geometries must be split to single parts. Then use the m-values
    from the single part geometries to create new routes. This is done to avoid
    non-monotonic routes. Non-monotonicity can be introduced from multipart
    outputs in GP tools, which are likely to reorder the part indices
    """
    dom_table_path  = os.path.join(
        os.path.dirname(input_routes),
        'dominant_table'
    )
    dom_events_path = os.path.join(
        os.path.dirname(input_routes),
        'dominant_event'
    )
    erased_routes_path = os.path.join(
        os.path.dirname(input_routes),
        'milepoint_concurr_gaps'
    )
    erased_routes_singlepart_path = os.path.join(
        os.path.dirname(input_routes),
        'milepoint_concurr_gaps_singlepart'
    )
    merged_routes_path = os.path.join(
        os.path.dirname(input_routes),
        'milepoint_dom_event_merge'
    )
    output_path = os.path.join(
        os.path.dirname(input_routes),
        'milepoint_dominant_network'
    )

    if overwrite_flag and arcpy.Exists(dom_table_path):
        print(' overwrite deleting {}'.format(dom_table_path))
        arcpy.Delete_management(dom_table_path)
    if overwrite_flag and arcpy.Exists(dom_events_path):
        print(' overwrite deleting {}'.format(dom_events_path))
        arcpy.Delete_management(dom_events_path)
    if overwrite_flag and arcpy.Exists(erased_routes_path):
        print(' overwrite deleting {}'.format(erased_routes_path))
        arcpy.Delete_management(erased_routes_path)
    if overwrite_flag and arcpy.Exists(merged_routes_path):
        print(' overwrite deleting {}'.format(merged_routes_path))
        arcpy.Delete_management(merged_routes_path)
    if overwrite_flag and arcpy.Exists(output_path):
        print(' overwrite deleting {}'.format(output_path))
        arcpy.Delete_management(output_path)

    if not overwrite_flag and arcpy.Exists(merged_routes_path):
        field_names = [field.name for field in arcpy.ListFields(merged_routes_path)]
        if all(['m_min' in field_names, 'm_max' in field_names]):
            add_m_fields = False
        else:
            add_m_fields = True

    # Begin geoprocessing logic
    if not arcpy.Exists(dom_table_path):
        print('\nSubsetting concurrency table to only dominant events')
        where_clause = "(DominantFlag = 1) AND (DominantError <> 4)"
        print(' {}'.format(where_clause))
        arcpy.TableToTable_conversion(
            concurrency_table,
            os.path.dirname(dom_table_path),
            os.path.basename(dom_table_path),
            where_clause=where_clause
        )

    if not arcpy.Exists(dom_events_path):
        print('\nCreating event for route dominance')
        line_props = 'RouteId LINE FromMeasure ToMeasure'
        dom_layer = arcpy.MakeRouteEventLayer_lr(
            input_routes, 'ROUTE_ID', dom_table_path, line_props,
            'dom_layer'
        )
        arcpy.CopyFeatures_management(
            dom_layer, dom_events_path
        )
        print(' {}'.format(dom_events_path))

    if not arcpy.Exists(erased_routes_path):
        print('\nCreating network gaps at concurrencies')
        arcpy.Erase_analysis(input_routes, dom_events_path, erased_routes_path)
        print(' {}'.format(erased_routes_path))

    if not arcpy.Exists(erased_routes_singlepart_path):
        print('\nSplitting gapped network to single-part geometries')
        arcpy.MultipartToSinglepart_management(
            erased_routes_path, erased_routes_singlepart_path
        )
        print(' {}'.format(erased_routes_singlepart_path))


    if not arcpy.Exists(merged_routes_path):
        print('\nMerging dominant routes with gapped network')
        field_mapping = map_fields(
            [erased_routes_singlepart_path, 'ROUTE_ID'],
            [dom_events_path, 'RouteId'],
            'ROUTE_ID'
        )
        arcpy.Merge_management(
            [erased_routes_singlepart_path, dom_events_path],
            merged_routes_path,
            field_mapping
        )
        print(' {}'.format(merged_routes_path))
        add_m_fields = True

    if add_m_fields:
        print('\nAdding m-values to the merged routes attribute table')
        merged_field_names = [
            field.name for field in arcpy.ListFields(merged_routes_path)
        ]
        if not 'm_min' in merged_field_names:
            arcpy.AddField_management(
                merged_routes_path,
                'm_min',
                'DOUBLE',
            )
            print(' created field: m_min')
        if not 'm_max' in merged_field_names:
            arcpy.AddField_management(
                merged_routes_path,
                'm_max',
                'DOUBLE',
            )
            print(' created field: m_max')
        update_fields = ['SHAPE@', 'm_min', 'm_max']
        with arcpy.da.UpdateCursor(merged_routes_path, update_fields) as update_cursor:
            for row in update_cursor:
                shape = row[0]
                m_min = shape.extent.MMin
                m_max = shape.extent.MMax
                update_cursor.updateRow([shape, m_min, m_max])
        print(' table update: complete')
    
    if not arcpy.Exists(output_path):
        print('\nCreating final network of only dominant routes')
        arcpy.CreateRoutes_lr(
            merged_routes_path, 'ROUTE_ID', output_path,
            measure_source='TWO_FIELDS',
            from_measure_field='m_min', to_measure_field='m_max',
            build_index='INDEX'
        )
        print(' {}'.format(output_path))


def map_fields(table_field_a, table_field_b, output_name):
    field_mappings = arcpy.FieldMappings()

    field_map = arcpy.FieldMap()
    field_map.addInputField(*table_field_a)
    field_map.addInputField(*table_field_b)
    output_field = field_map.outputField
    output_field.name = output_name
    field_map.outputField = output_field
    field_mappings.addFieldMap(field_map)

    return field_mappings


def main():
    input_routes =      r'D:\Pavement\Input_Data\Milepoint.gdb\Milepoint_20190116'
    concurrency_table = r'D:\Pavement\Input_Data\Milepoint.gdb\route_concurrencies_tvd20190116'
    overwrite_flag =    False

    make_dominant_network(input_routes, concurrency_table, overwrite_flag=overwrite_flag)


if __name__ == '__main__':
    start_time = datetime.datetime.now()
    print('Running script: {0}\nStart time: {1}'.format(
        os.path.abspath(__file__), start_time)
    )

    main()

    end_time = datetime.datetime.now()
    print('\nCompleted at: {}.\nTime to complete: {}'.format(
        end_time, end_time - start_time)
    )
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
1 Solution

Accepted Solutions
AmitHazra
Esri Contributor

Hello Andrew-

Yes you are almost there. If the intent is  to create a new route feature class with non-overlapping primary (dominant) routes only you'll want to add a few more steps to this process.

After the erase you'll most likely need to run multi-part to single part before you re-assemble the non-overlapping sections with the sections you've created as a result of the CRC process. After that, you'll append those two sources and then if you need to produce routes the easiest way to complete this would be to calculate MMin and MMax fields based on each feature part and then use the core linear referencing Create Routes GP tool.

Here's what my GP Model would look like:

The expression on the table view is: DominantFlag = 1 and DominantError <> 4

Hope that helps,

Amit @ Esri

View solution in original post

12 Replies
AmitHazra
Esri Contributor

Hello Andrew-

Yes you are almost there. If the intent is  to create a new route feature class with non-overlapping primary (dominant) routes only you'll want to add a few more steps to this process.

After the erase you'll most likely need to run multi-part to single part before you re-assemble the non-overlapping sections with the sections you've created as a result of the CRC process. After that, you'll append those two sources and then if you need to produce routes the easiest way to complete this would be to calculate MMin and MMax fields based on each feature part and then use the core linear referencing Create Routes GP tool.

Here's what my GP Model would look like:

The expression on the table view is: DominantFlag = 1 and DominantError <> 4

Hope that helps,

Amit @ Esri

AndrewVitale3
New Contributor III

Thanks, Amit. This is very helpful.

My "model" is essentially the same as yours except that I'm using Merge/Dissolve rather than Multipart to Singlepart/Append. I've also done away with the template feature class, since all I need from the LRS is the route ID and geometry (the end game is simply to build a feature class of an external event table).

I'm still struggling to understand why you suggest creating new routes as a last step. Since I need the original m-values from the LRS to properly locate my external events, isn't it best to leave the native m-values?

Thanks again,

Andrew

AmitHazra
Esri Contributor

Hello Andrew,


In my experience the Erase GP tool will typically create multi-part features. Reassembling the multi-part features with the output from Calculate Route Concurrency (CRC) dynamic segmentation using the merge/dissolve pattern will also continue to produce multi-part features where the part orders may be jumbled (Dissolve GP is mostly the common offender here). So although the geometry of the routes may be intact and the vertices are still carrying the original route m-values, the GP-derived route parts are not ordered correctly and would yield incorrect geometries if the features were used as input routes in Make Route Event Layer (MREL).

In order to create a monotonic derived LRS feature class where there are no route concurrencies and only the dominant routes are included I've found that exploding the erased multi-parts into single parts and then appending them to the erased routes preserves the part orders and m-values such that I can then field calculate the MMin and MMax for each singlepart and use those fields and polylines to build a monotonic network in core linear referencing.

This kind of derived network might be used for the purpose of say... feature based location of geometries that you wish to associate to the R&H LRS. I'm sure there are other reporting and publication uses as well where you wish to inform users or systems of only the routes that carry inventory/event information.

Regardless of the process you end up utilizing, I highly suggest you quality assure your derived network using the Data Reviewer Monotonicity Check in Polyline Checks.

Amit @ Esri

AndrewVitale3
New Contributor III

Amit,

Thanks so much for taking the time to write that up. It's a great explanation that likely saved me hours of debugging.

About 11,000 of my 195,000 routes are non-monotoic with my current workflow. I'll implement your suggestions and put the python script in here for others.

Cheers,

Andrew

ErinLesh1
New Contributor III

Andrew - NCDOT has a SQL script we use that we can share with you. 

Also, something important to state along with this topic - a route may be dominant along part of it's length and subordinate along a different part of it's length. 

AndrewVitale3
New Contributor III

Erin,

Thanks for the response. I'd definitely be interested in taking a peak at the SQL script.

While we're on the subject of important things related to this topic, the Calculate Route Concurrency tool is only as good as your centerlines.

We currently have some duplicate centerlines in our LRS. Since we're constantly editing the LRS, we've been having a hard time working the Remove Duplicate Centerlines task into our business practice. The concurrent routes that are on duplicate centerlines will not show up as concurrencies in the CRC output.

Thanks,

Andrew

JustinAdams2
New Contributor

Amit, 

 

I followed your model for creating a new route feature class with non-overlapping primary routes.  I have discovered that this new feature classes m-values do not match the network it was derived from along all parts of a route.  Smaller routes may match up in their entirety but longer routes my drift.  In other words, the min and max m-values are fine, but the m-vales between the two networks my not match between the min and max m-values.  The appended data in the network template feature class matches exactly, but after running the create routes geoprocessing tool from the MMin and MMax values the error is introduced.  I may be able to eliminate the error by: taking the original network and running feature vertices to points, then locate features along routes on the output, then make route event layer, then add xy coordinates, then run delete identical based on routeid and xy coordinates, as well as, delete all points where RID does not match ROUTEID, and then finally use those points to calibrate the new primary routes network.  This seems a bit convoluted and the processing time will take a couple of days.  Any thoughts?

 

Thank you,

 

Justin.

0 Kudos
AmitHazra
Esri Contributor

Hi Justin - Interesting find. Can you validate whether the spatial parameters for the GP environment using Create Routes matches your ALRS? 

0 Kudos
JustinAdams2
New Contributor

Good day,

Just finished the process with the environmental settings locked down.  I set the XY resolution and tolerance, also set the M resolution and tolerance to match the ALRS.  Unfortunately, the error is still introduced after running the create routes geoprocessing tool.  Do you see this on your end?  The specific route I am looking at has a calibrated length of 13.09 miles and a geometric length of 12.876342 miles. 

Thanks,

Justin.

0 Kudos