Cecilia Trail 2024

Fifteen NMVFO volunteers returned for the second project of the year on Cecilia Trail, in the San Pedro Parks Wilderness, to finish clearing downed trees from the route. Several cairns were also constructed, where the tread typically fades through meadows.

We camped at Resumidero Camping Area, a huge open meadow where large local family groups often camp out for several weeks during the summer.

We divided up into three teams, with two 2-person crosscut saws and one 1-person crosscut rigged for 2-person mode, assisted by KatanaBoys and smaller folding saws.

We had a few new volunteers, and a couple of young adults, and it was a pleasure to introduce them to the crosscut.

Six Mile Trail 2024

Volunteers from NMVFO and Socorro Trails gathered for a weekend project to open up the Six Mile Trail / Ryan Hill Trail loop, which used to be popular for mountain bikers, and was even published in a guide, but is now thoroughly overgrown. Optional camping started Friday evening at Water Canyon Group Campsite, with its impressive laminated curved beams.

Saturday we carpooled up FR 235/ Water Canyon Rd to the trailhead at Six Mile Trail. A huge crane had slid off an icy road a few months ago, just a hundred yards up from the trailhead, so several of us walked up to take a look. It is hard to judge size from the picture, and I could not convince C. to climb down to be in the photo for scale, but the wreck is pretty huge.

We divided into 3 4-person teams, with one group doing sawing while the others lopped. Wild raspberries were in season.

At the trail junction we diverted to Ryan Hill Trail, with some lopping, and hiked out to cars we had left, to shuttle us back to camp.

The next day 8 volunteers returned to Ryan Hill Trailhead, and lopped for a half-day, not reaching the intersection with Six Mile, but doing a thorough job of beating back the oak and mountain mahogany.

Related Posts:

Trails and Graph Theory 21: More Databook

In a previous article we sketched out how to create a databook for our long hike, a text summary of the route, which can include waypoints, elevation, trail names, and intersections. Our first result was somewhat crude, so we are circling back to make a better effort.

In a recent post we described how to break apart trail sections that had been concatenated together as part of our algorithm to find a maximum length route. We also found a different way to add elevation to our network, using built-in OSMNx functions. We have so many nodes in our desimplified network graph that it is better to take steps to only add elevation to important nodes.

def add_elevation(Q):
    H = nx.MultiGraph()
    H.graph['crs'] = ox.settings.default_crs

    for node, dat in Q.nodes(data=True):
        if 'street_count' in dat:
            street_count = dat['street_count']
            if street_count > 2:
                H.add_node(node,**dat)
    ox.settings.elevation_url_template = \
        'https://api.open-elevation.com/api/v1/lookup?locations={locations}'
    ox.elevation.add_node_elevations_google(H, api_key=None,
        batch_size=350,
        pause=1.0,
            )
    print('Added elevation data for ',H.number_of_nodes(), ' nodes')
    for node,dat in H.nodes(data=True):
        Q.add_node(node,**dat)
    return Q

Another improvement we should make is to document any road crossing or trail crossing on the route. Along with our trail graph G, we use the graph of all trails and roads GTR. Whenever a G node has a ‘street_count’ attribute greater than 2, we expect a crossing, and go back to GTR to attempt to find the name of the road or trail that our route crosses.

def report_path(Q,Q_orig, txt='',freedom_units=False,lat_long=False):
    meters_to_mile = 0.0006213712
    meters_to_feet = 3.28084
    meters_to_km = 0.001
    
    if nx.is_eulerian(Q):
        total_length = 0
        data = []
        
        for u,v,k in nx.eulerian_circuit(Q, keys=True): #fix: use predefined circuit, not arbitrary
             data_line = []
             if freedom_units:
                 data_line.append(str(round(total_length*meters_to_mile,1)))
             else:
                 data_line.append(str(round(total_length*meters_to_km,1)))
                 
             d = Q.get_edge_data(u,v)[k]
            # print(d)
             length=0                 
             if 'length' in d:
                 length = d['length'] #meters
                 total_length += length
             node_attr = Q.nodes(data=True)[v]
            # print( node_attr )
            
             street_count = node_attr['street_count']
             if street_count == 2:
                 continue
             
             long = node_attr['x']
             lat = node_attr['y']
             if lat_long:
                 data_line.append(lat)
                 data_line_append(long)
             #elevation = get_elevation(lat,long) #in meters
             if 'elevation' in node_attr:
                 elevation = node_attr['elevation']
                 if freedom_units:
                     data_line.append(str(round(elevation*meters_to_feet)))
                 else:
                     data_line.append(str(round(elevation)))
             else:
                 data_line.append(' ')

             trail_name=''
             if 'name' in d:
                 trail_name = d['name']
                 if not isinstance(trail_name, str):
                     trail_name = ','.join(trail_name) #in case is a list
             
             data_line.append(trail_name)                     
             
             crossing = set()
             crossing_txt = ''
             for x,neighbor,k,d in Q_orig.edges(v,data=True, keys=True):
                 if 'name' in d:
                     name = d['name']
                     if not isinstance(name, str): #FIX with a foreach
                         name = ','.join(name) #in case is a list
                     if name != trail_name:
                         crossing.add(name)
                 elif 'highway' in d:
                     name = d['highway']
                     name = '<' + name + '>'
                     crossing.add(name)
             if len(crossing)>0:
                 crossing_txt = ','.join(crossing)
             data_line.append(crossing_txt)
                              
             data.append(data_line)                 
        header = []
        if freedom_units:
            header.append('MILE')
        else:
            header.append('KM')
        if lat_long:
            header.append('LAT')
            header.append('LONG')
        header.append('ELEVATION')
        header.append('TRAIL')
        header.append('INTERSECTION')
        table = columnar(data, header, no_borders=True,terminal_width=132)
        print(table)     
    else:
        print('The graph is not eulerian')

Ideally, it might be useful to automatically add nearby water sources. We did document a way to find water sources, but inserting them into our databook looks challenging, so perhaps we will add that feature at a later date. Algorithmically adding other features, such as post offices, stores, or established campsites, are left as an exercise for the reader. 😁

A short example excerpt from the databook shows the improvements:

  MILE   ELEVATION  TRAIL                                    INTERSECTION                                                      

234.6 8373 West Fork Trail #151 Iron Creek Trail #172
234.8 8376 West Fork Trail #151 Iron Creek Mesa Trail #171
237.4 7831 West Fork Trail #151 Cooper Trail #141
240.3 7841 Cooper Trail #141 Clayton Mesa Trail #175
242.6 7106 Clayton Mesa Trail #175 Middle Fork Trail #157
242.7 7090 Middle Fork Trail #157 Iron Creek Mesa Trail #171
246.0 7326 Middle Fork Trail #157 Snow Canyon Trail #142
246.6 7467 Snow Canyon Trail #142 <path>
246.8 7356 Snow Canyon Trail #142 <path>
246.9 7359 Snow Canyon Trail #142 <service>

Download source code and improved databook here.

Related Posts: