3

I've recently started tracking bike treks using Strava and I thought it would be interesting to do some analysis on my treks. I am able to import the GPX file into ArcMap and convert it to a point file, and then convert the point file to a line file.

However, when you do that, ArcMap creates a single-part line that has no attribute information. I can split the line into a multi-part line file using the Split Line at Vertices tool, but I am unsure of how to get information about the vertices (point file) for each line segment. The main statistic I want from the point file is the amount of time traversed on each segment. I have time data in the point field, but I am unsure how to get that information moved over to the line file.

I have ArcGIS 10.1 Advanced License as well as QGIS installed, and I am comfortable with ArcPy, ModelBuilder and the QGIS python libraries.

I've screenshotted some of my data below:

enter image description here

enter image description here

enter image description here

  • why not spatial join point with line segments you would have you line segment with attribute something Datetime1 - Datetime2 – GIS Data Butcher Jun 21 '15 at 5:32
  • 1
    Sounds like a use case for PolylineM geometry type, with M being the timestamp of your GPS recording. – Detlev Jun 21 '15 at 16:06
  • Totally agree with @Detlev, import X,Y,Z,T as X,Y,Z,M line.. the issue though is what are you trying to do with the time information in the line? If the path is going to be collection of 2-part lines why not just show the points behind the line? Is it visual (labels only) or is there some type of analysis you're trying to perform? – Michael Stimson Jun 21 '15 at 22:04
  • I want to have a time elapsed for each segment, so I can look at where my peak speeds are and how my speed changes on inclines so I can start improving my cycling more. – Alex McVittie Jun 22 '15 at 1:02
5

Provided that the line segments have ObjectIDs numbered in the same order as the points and all points have sequential ObjectIDs, you can add a field called EndOID to your lines and calculate it to be equal to ObjectID + 1. Also add two dateTime fields called Start_Time and End_Time and a double field called Duration to your lines. Do a standard join of the line's ObjectID to the point's ObjectID and calculate the points DateTime field in to the Start_Time field of the line, then break the join. Next do a standard join of the line's EndOID field to the point's ObjectID and calculate the points DateTime field in to the End_Time field of the line, then break the join. Finally calculate the line's Duration field as (End_Time - Start_Time) * 24 (assuming you want each whole number to be an hour of time elapsed).

If you want to create a polylineZM geometry as Detlev suggested for the whole line, so that you can use dynamic segmentation to show any portion of your trip based on elapsed time do the following:

Use the GPX to Points tool as you have done, but in the Environment settings for the tool set the "Output has M Values" setting to "Enabled" and the "M Resolution" setting to be less than or equal to 0.00002 and "M Tolerance" setting to be less than or equal to 0.0002 (if you want the time elapsed for each whole number measure value to be based on a unit of one hour of travel time and support one second as the smallest measure portion at 0.0002777... of one hour).

Next add a double field to the points called Elapsed_Time and using VB Script in the Field Calculator calculate it to be equal to the point's time minus the starting time of the trip multiplied by 24, which for this specific data should be: (DateTime - #6/20/2015 4:13:08 PM#) * 24. Using an update cursor you can transfer the ElapsedTime values to your point's M coordinate.

Now when you run the Points to Line tool on the points the line will be a PolylineZM line and will retain the Z and M values of the points. Then you can create events by using a Route ID (presumably the NAME field) and any single measure in the time elapsed for a point or any two measures of elapsed time for a line segment. You can also use hatching for showing regular intervals of time elapsed. If you segmented the line using the Split Line at Vertices tool you simply extract the StartElapsedTime and EndElapsedTime of each segment using the Python Field Calculations: !Shape.FirstPoint.M! and !Shape.LastPoint.M!

The script below has been tested and worked for my data where I was trying to create one line from my gpx points. I found I could not add M coordinates using the GPX To Point tool or the Project Tool, so I had to use the Feature Class to Feature Class tool to get the points to have M coordinates. I had to project the points to a Projected Coordinate System, since I needed lengths in linear units to be able to get slope and speed information for the data. I could not output to a shapefile with this code, because shapefiles do not support date/time values in their date fields, so I used a fgdb to retain the full data/time value. My GPX date/time field was a string in UTC format that I converted to a real date field for my time zone. I also have created an event table that creates line segments. The segments include fields for symbolizing the average elevation, slope and speed on each segment. More code would be needed to read and reset the beginning time of each new line if the points represented several lines separated by the Name field:

from datetime import datetime

a = datetime.now()                 
s = (a - a).seconds
print 'Script Started - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

import arcpy
from arcpy import env
from dateutil import tz
import math

s = (datetime.now() - a).seconds
print 'Imports Completed - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

# Set the workspace environment setting
env.workspace = r'\\agency\agencydfs\CustomApps\rfairhur\TCDI DATA\TRAFFIC_TCDI_NEW.gdb' # could be an input parameter

# Set the environment settings for M Values and the output coordinate system and transformation
env.outputMFlag = 'Enabled'
env.MResolution = 0.00001
env.MTolerance = 0.0001
env.outputCoordinateSystem = arcpy.SpatialReference('NAD 1983 StatePlane California VI FIPS 0406 (US Feet)')
env.geographicTransformation = 'WGS_1984_(ITRF00)_To_NAD_1983'

# Units and Conversion Factors
linearUnits = 'Ft'
metersPerLinearUnit = 0.3048006096012192
kmOrMiPerLinearUnit = 5280
timeUnits = 'Mins'
timeUnitsPerDay = 24 * 60
timeUnitsPerHour = 60
speedUnits = 'mph'
stableSpeedVariance = 0.5
offsetDist = 100

# Input GPX Feature Class
InputGPX = r'C:\Users\nahmad.AGENCY\Desktop\VIDE_GPS JV\VIRB_0006_2015-06-04.gpx' # Could be an input file parameter

# Starting Date and Time of the trip captured by the GPX points
startDate = '06/04/2015'
startTime = '11:13:19 AM' # Could be a date time string parameter

# Output Feature Class and Table Names
PointFileRaw = 'PointFileRaw' # could be an output file name parameter
PointFile = 'PointFile' # could be an output file name parameter
SingleLine = 'SingleLine' # could be an output file name parameter
Segments = 'Segments'
SegmentLines = 'SegmentLines'
SegmentLinesLeft = 'SegmentLinesLeft'
SegmentLinesRight = 'SegmentLinesRight'

# Variables for GPX Field Names
ObjectID = 'OBJECTID'
Name = 'Name'
DateTimeS =  'DateTimeS'
Elevation = 'Elevation'
# Variables for Custom Added Field Names
From_OID = 'From_OID'
Point_OID = 'Point_OID'
To_OID = 'To_OID'
From_DateTime = 'From_DateTime'
To_DateTime = 'To_DateTime'
From_Time = 'From_Time_Mins'
To_Time = 'To_Time_Mins'
Segment_Time = 'Segment_Time_Mins'
From_Elevation = 'From_Elevation_Ft'
To_Elevation = 'To_Elevation_Ft'
Avg_Elevation = 'Avg_Elevation_Ft'
Segment_Rise = 'Segment_Rise_Ft'
Uphill_Downhill = 'Uphill_Downhill'
Slope = 'Slope'
Length2D = 'Length_2D'
Length3D = 'Length_3D'
From_Speed = 'From_Speed_MPH'
To_Speed = 'To_Speed_MPH'
Faster_Slower = 'Faster_Slower'
Left_Offset = 'Left_Offset'
Right_Offset = 'Right_Offset'
ShapeM = 'SHAPE@M'
ShapeZ = 'SHAPE@Z'

s = (datetime.now() - a).seconds
print 'Variables Read - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

if arcpy.Exists(PointFileRaw):
    arcpy.Delete_management(PointFileRaw)

arcpy.GPXtoFeatures_conversion(InputGPX, PointFileRaw)

s = (datetime.now() - a).seconds
print 'PointFileRaw Created - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

# Add Fields to the PointFileRaw points
arcpy.AddField_management(PointFileRaw, From_OID, 'LONG')
arcpy.AddField_management(PointFileRaw, Point_OID, 'LONG')
arcpy.AddField_management(PointFileRaw, To_OID, 'LONG')
arcpy.AddField_management(PointFileRaw, From_DateTime, 'DATE')
arcpy.AddField_management(PointFileRaw, To_DateTime, 'DATE')
arcpy.AddField_management(PointFileRaw, From_Time, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, To_Time, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, Segment_Time, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, From_Elevation, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, To_Elevation, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, Avg_Elevation, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, Segment_Rise, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, Uphill_Downhill, 'LONG')
arcpy.AddField_management(PointFileRaw, Slope, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, Length2D, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, Length3D, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, From_Speed, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, To_Speed, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, Faster_Slower, 'LONG')
arcpy.AddField_management(PointFileRaw, Left_Offset, 'DOUBLE')
arcpy.AddField_management(PointFileRaw, Right_Offset, 'DOUBLE')

s = (datetime.now() - a).seconds
print 'PointFileRaw Fields Added - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

domains = arcpy.da.ListDomains(env.workspace)

hasUphillDownhill = False
hasFasterSlower = False
for domain in domains:
    if Uphill_Downhill == domain.name:
        hasUphillDownhill = True
    if Faster_Slower == domain.name:
        hasFasterSlower = True

if not hasUphillDownhill:
    arcpy.CreateDomain_management(env.workspace, Uphill_Downhill, "Segment Elevation is Uphill, Downhill or Flat", "LONG", "CODED")
    domDict = {-1 : "Downhill", 0 : "Flat", 1 : "Uphill"}
    for code in domDict:        
        arcpy.AddCodedValueToDomain_management(env.workspace, Uphill_Downhill, code, domDict[code])
arcpy.AssignDomainToField_management(PointFileRaw, Uphill_Downhill, Uphill_Downhill)

if not hasFasterSlower:
    arcpy.CreateDomain_management(env.workspace, Faster_Slower, "Speed is Faster, Slower or Stable", "LONG", "CODED")
    domDict = {-1 : "Slower", 0 : "Stable", 1 : "Faster"}
    for code in domDict:        
        arcpy.AddCodedValueToDomain_management(env.workspace, Faster_Slower, code, domDict[code])
arcpy.AssignDomainToField_management(PointFileRaw, Faster_Slower, Faster_Slower)

s = (datetime.now() - a).seconds
print 'PointFileRaw Domains Added - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

fields = [ObjectID, From_OID, Point_OID, To_OID,
          DateTimeS, From_DateTime, From_Elevation,
          Elevation, Left_Offset, Right_Offset]  

# Get system Time Zome information for the UTC and local time zones.
from_zone = tz.tzutc()
to_zone = tz.tzlocal()

# convert the starting date and time into a DateTime in the local time zone
tripStartTime = datetime.strptime(startDate + ' ' + startTime, '%m/%d/%Y %H:%M:%S %p')
tripStartTime = tripStartTime.replace(tzinfo=to_zone)

# update PointFileRaw with user friendly local data derived directly from the GPX data.
with arcpy.da.UpdateCursor(PointFileRaw, fields) as cursor:
    for row in cursor:
        # From_OID = ObjectID - 1
        row[1] = row[0] - 1
        # Point_OID = ObjectID
        row[2] = row[0]
        # From_OID = ObjectID + 1
        row[3] = row[0] + 1
        # Convert DateTimeS from a UTC string to a local DateTime
        utc = datetime.strptime(row[4], '%Y-%m-%dT%H:%M:%SZ')
        utc = utc.replace(tzinfo=from_zone)
        localTime = utc.astimezone(to_zone)
        # From_DateTime = DateTimeS converted to local time
        row[5] = localTime
        # From_Elevation = Elevation / metersPerLinearUnit
        row[6] = row[7] / metersPerLinearUnit
        # Left_Offset = offsetDist
        row[8] = offsetDist
        # Right_Offset = offsetDist * -1
        row[9] = offsetDist * -1
        cursor.updateRow(row)

s = (datetime.now() - a).seconds
print 'First UpdateCursor Completed - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

arcpy.CalculateField_management(PointFileRaw, From_Time, '([' + From_DateTime + '] - #' + startDate + ' ' + startTime + '#) * ' +  str(timeUnitsPerDay), 'VB')

s = (datetime.now() - a).seconds
print 'FromTime Calculation Completed - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

fields = [ObjectID, From_OID, Point_OID, To_OID,
          DateTimeS, From_DateTime, From_Elevation,
          Elevation, Left_Offset, Right_Offset, From_Time]  

valueDict = {r[0]:(r[1:]) for r in arcpy.da.SearchCursor(PointFileRaw, fields)}

s = (datetime.now() - a).seconds
print 'Values Read to Dictionary - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

if arcpy.Exists(PointFile):
    arcpy.Delete_management(PointFile)

arcpy.FeatureClassToFeatureClass_conversion(PointFileRaw, env.workspace, PointFile)

s = (datetime.now() - a).seconds
print 'PointFile Created - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

fields = [ShapeM, From_Time, ShapeZ, From_Elevation,
          To_OID, To_DateTime, To_Elevation, To_Time,
          Segment_Time, Avg_Elevation, Segment_Rise, Uphill_Downhill]

with arcpy.da.UpdateCursor(PointFile, fields) as cursor:
    for row in cursor:
        # Shape@M = From_Time
        row[0] = row[1]
        # Shape@Z = From_Elevation
        row[2] = row[3]
        # only update values if To_OID values is a key in the valueDict dictionary
        if row[4] in valueDict:
            # To_DateTime = Next OID's From_DateTime
            row[5] = valueDict[row[4]][4]
            # To_Elevation = Next OID's From_Elevation
            row[6] = valueDict[row[4]][5]
            # To_Time = Next OID's From_Time
            row[7] = valueDict[row[4]][9]
            # Segment_Time = To_Time - From_Time
            row[8] = row[7] - row[1]
            # Avg_Elevation = (From_Elevation + To_Elevation) / 2
            row[9] = (row[3] + row[6]) / 2
            # Segment_Rise = To_Elevation - From_Elevation
            row[10] = row[6] - row[3] 
            # if Segment_Rise != 0 and Segment_Rise != None:
            if row[10] != 0 and row[10] != None:
                # Uphill_Downhill = (abs(!Segment_Rise!) > 0 and True) * !Segment_Rise! / abs(!Segment_Rise!)
                row[11] = (abs(row[10]) > 0 and True) * row[10] / abs(row[10])
            else:
                row[11] = 0
        cursor.updateRow(row)

s = (datetime.now() - a).seconds
print 'Second UpdateCursor Completed - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

layerName = PointFile + 'lyr'

arcpy.MakeFeatureLayer_management(PointFile,  layerName)

arcpy.AddIndex_management(layerName, From_OID, From_OID)

arcpy.AddIndex_management(layerName, Point_OID, Point_OID)

arcpy.AddIndex_management(layerName, To_OID, To_OID)

s = (datetime.now() - a).seconds
print 'PointFile Indexes Added - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

arcpy.MakeFeatureLayer_management(PointFile, PointFile + '_Layer')

if arcpy.Exists(Segments):
    arcpy.Delete_management(Segments)

inTable = PointFile
outLocation = env.workspace
outTable = Segments

# Set the expression, with help from the AddFieldDelimiters function to select the appropriate field delimiters for the data type
expression = ''

# Execute TableToTable
arcpy.TableToTable_conversion(inTable, outLocation, outTable, expression)

s = (datetime.now() - a).seconds
print 'Segments Table Created - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

arcpy.AddIndex_management(outTable, Name, Name)

arcpy.AddIndex_management(outTable, From_OID, From_OID)

arcpy.AddIndex_management(outTable, Point_OID, Point_OID)

arcpy.AddIndex_management(outTable, To_OID, To_OID)

arcpy.AddIndex_management(outTable, Left_Offset, Left_Offset)

arcpy.AddIndex_management(outTable, Right_Offset, Right_Offset)

s = (datetime.now() - a).seconds
print 'Segment Indexes Added - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

if arcpy.Exists(SingleLine):
    arcpy.Delete_management(SingleLine)

arcpy.PointsToLine_management(PointFile, SingleLine, Name, '')

from datetime import datetime
s = (datetime.now() - a).seconds
print 'SingleLine Created - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

eventlayerName = Segments + '_Events'

routeFields = '{} LINE {} {}'.format(Name, From_Time, To_Time)

arcpy.MakeRouteEventLayer_lr (SingleLine, Name, Segments, routeFields, eventlayerName, '#', 'ERROR_FIELD', 'ANGLE_FIELD')

arcpy.CalculateField_management(eventlayerName, Length2D, '!Shape.Length!', 'PYTHON_9.3')

s = (datetime.now() - a).seconds
print 'Length2D of Segment Events Calculated - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

fields = [Length3D, Length2D, Segment_Rise, Slope,
          To_Speed, From_Time, To_Time]

with arcpy.da.UpdateCursor(Segments, fields) as cursor:
    for row in cursor:
        # print 'Length2D = ' + str(row[1]) + ' Segment_Rise = ' + str(row[2]) + ' From_Time = ' + str(row[5]) + ' To_Time = ' + str(row[6])
        if row[1] != None and row[2] != None and row[5] != None and row[6] != None:
            # Length3D = math.sqrt(math.pow(!Length2D!, 2) + math.pow(!Segment_Rise!, 2))
            row[0] = math.sqrt(math.pow(float(row[1]), 2) + math.pow(float(row[2]), 2))
            # Slope = !Segment_Rise! / !Length3D! * 100
            row[3] = row[2] / row[0] * 100
            # To_Speed = (!Length3D! / kmOrMiPerLinearUnit) / (abs(!From_Time! - !To_Time!) / timeUnitsPerHour)
            row[4] = (row[0] / kmOrMiPerLinearUnit) / (abs(row[5] - row[6]) / timeUnitsPerHour) 
        cursor.updateRow(row)

s = (datetime.now() - a).seconds
print 'Third UpdateCursor Completed - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

arcpy.MakeTableView_management(Segments,  Segments)

joinField = From_OID
joinTable = eventlayerName
joinField2 = Point_OID

arcpy.AddJoin_management(Segments, joinField, joinTable, joinField2)

arcpy.CalculateField_management(Segments, Segments + '.' + From_Speed, '[' + Segments + '_Features.' + To_Speed + ']', 'VB')

arcpy.RemoveJoin_management(Segments)

s = (datetime.now() - a).seconds
print 'Segments From_Speed Calculated - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

fields = [To_Speed, From_Speed, Faster_Slower]

with arcpy.da.UpdateCursor(Segments, fields) as cursor:
    for row in cursor:
        # print 'Length2D = ' + str(row[1]) + ' Segment_Rise = ' + str(row[2]) + ' From_Time = ' + str(row[5]) + ' To_Time = ' + str(row[6])
        if row[0] == None:
            # To_Speed = 0
            row[0] = 0
        if row[1] == None:
            # From_Speed = 0
            row[1] = 0
        if row[0] - row[1] != 0:
            # To_Speed = (abs(!' + To_Speed + '! - !' + From_Speed + '!) > stableSpeedVariance and True) * (!' + To_Speed + '! - !' + From_Speed + '!) / abs(!' + To_Speed + '! - !' + From_Speed + '!)
            row[2] = (abs(row[0] - row[1]) > stableSpeedVariance and True) * (row[0] - row[1]) / abs(row[0] - row[1])
        else:
            row[2] = 0
        cursor.updateRow(row)

s = (datetime.now() - a).seconds
print 'Segments Faster_Slower Calculated - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

fields = [Point_OID, Length2D, Length3D, Slope,
          From_Speed, To_Speed, Faster_Slower]

valueDict = {r[0]:(r[1:]) for r in arcpy.da.SearchCursor(layerName, fields)}

with arcpy.da.UpdateCursor(layerName, fields) as cursor:
    for row in cursor:
        keyValue = row[0]
        for n in range (1,len(fields)):  
            row[n] = valueDict[keyValue][n-1]  
        cursor.updateRow(row)

s = (datetime.now() - a).seconds
print 'Fourth UpdateCursor Completed - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

if arcpy.Exists(SegmentLines):
    arcpy.Delete_management(SegmentLines)

arcpy.FeatureClassToFeatureClass_conversion(eventlayerName, env.workspace, SegmentLines)

s = (datetime.now() - a).seconds
print 'SegmentLines Created - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

if arcpy.Exists(SegmentLinesLeft):
    arcpy.Delete_management(SegmentLinesLeft)

arcpy.MakeRouteEventLayer_lr (SingleLine, Name, Segments, routeFields, eventlayerName, Left_Offset, 'ERROR_FIELD', 'ANGLE_FIELD')

arcpy.FeatureClassToFeatureClass_conversion(eventlayerName, env.workspace, SegmentLinesLeft)

s = (datetime.now() - a).seconds
print 'SegmentLinesLeft Created - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

if arcpy.Exists(SegmentLinesRight):
    arcpy.Delete_management(SegmentLinesRight)

arcpy.MakeRouteEventLayer_lr (SingleLine, Name, Segments, routeFields, eventlayerName, Right_Offset, 'ERROR_FIELD', 'ANGLE_FIELD')

arcpy.FeatureClassToFeatureClass_conversion(eventlayerName, env.workspace, SegmentLinesRight)

s = (datetime.now() - a).seconds
print 'SegmentLinesRight Created - {:02}:{:02}:{:02}'.format(s // 3600, s % 3600 // 60, s % 60)

Here are screen shots of the Linear References Event Layers for a car traveling a road. It shows Average Elevation (in Feet where blue being lower elevations and red being higher elevations), Slope for west bound travel (where blue is uphill and red is downhill), and Speed (in Miles Per Hour where blue is for speeds slower and red is for speeds faster than the target average speed of 35 mph):

Average Elevation in Feet

Slope

Speed in Miles Per Hour

  • 2
    I have updated my answer. You do not need to use the Split Line at Vertices tool. The script creates an event table based on elapsed time measures and then creates the line segments using a Linear Referencing Event Line layer. – Richard Fairhurst Jun 25 '15 at 7:14
  • 2
    I have updated the code again to make it easier for users to change field names and units for time, length or speed. I also added fields that make it simple to display changes between uphill, downhill and flat elevations from one end of the segment to the other or between faster, slower and stable speeds for the segment relative to the previous segment. – Richard Fairhurst Jun 29 '15 at 2:12
  • 1
    I forgot to mention that I also modified the code so that it stores the 3D Lengths of the segments and bases slope and speed on the 3D lengths, not 2D lengths (although 2D lengths are also stored and could easily be used if desired). Finally I included a user defined offset distance that creates actual right and left offset polylines segments so that three different field values can be displayed side by side without using LR event layers. So combinations like Slope, Elevation and Uphill_Downhill values or Slope, Speed and Faster_Slower values can be displayed side by side. – Richard Fairhurst Jun 29 '15 at 5:20
  • We also acquired a separate Ball Bank device, which takes multiple readings each second when it detects a change in banking angles due to curves. I will be integrating that data with a separate script later. The time stamps of the banking data matched up perfectly to the GPX date/times in our trial run. I have developed a formula that indicates when a comfortable speed has been exceeded according to Figure 2C-101 (CA) in the California MUTCD 2014 Edition on page 305. The formula adjusts the threshold degrees based on the current speed of the segment for min, max and mean bank readings. – Richard Fairhurst Jun 30 '15 at 2:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.