Trails and Graph Theory 19: Elevation

In a previous post we imported elevation information into our graph and databook, using a JSON query to an open data website.

#https://stackoverflow.com/questions/68534454/python-obtaining-elevation-from-latitude-and-longitude-values/68540685#68540685
def get_elevation(lat, long):
    return 0
    query = ('https://api.open-elevation.com/api/v1/lookup'
             f'?locations={lat},{long}')
    r = requests.get(query).json()  # json object, various ways you can extract value
    elevation = pd.json_normalize(r, 'results')['elevation'].values[0]
    return elevation # returns in meters, not freedom units

Now, in preparation to revisiting our databook code, we will take advantage of the elevation module in OSMNX, reading in elevations for all nodes in the graph with one function call. (Even though the API call is add_node_elevations_google with ‘google‘ in the name, we are not required to use the Google service, for which I do not have a key).

ox.settings.elevation_url_template = 'https://api.open-elevation.com/api/v1/lookup?locations={locations}'

ox.elevation.add_node_elevations_google(J, api_key=None,
        batch_size=350,
        pause=1.0,
            )

(If you use the service at open-elevation.com, please throw them a donation.)

We can colorize our nodes and edges by elevation, making the crude approximation that an edge elevation is the mean elevation of its two nodes.

def colorize_elevation(Q):
    QR = Q.copy()
    for node, dat in Q.nodes(data=True):
        dat['color'] = dat['elevation']
        QR.add_node(node,**dat)                #replace node value

    for u, v, k, dat in Q.edges(keys=True,data=True):
        e1 = Q.nodes[u]['elevation']
        e2 = Q.nodes[v]['elevation']
        dat['color'] = (e1 + e2)/2.0
        QR.add_edge(u,v,key=k,**dat)
    QR.graph['color']=True
    return QR

A small change to our draw() function uses magma as our pre-defined cmap.

Because my graph is simplified, meaning many 2nodes are merged together, the elevation appears to have large steps. We will address this in the next post.

Download source code here.

Related Posts:

Trails and Graph Theory 18: Water Sources

Now that we have been able to create GPX files and a databook for our trip, a logical improvement would be to include water sources. OpenStreetMaps has several tags for identifying water, which seem to have been added over time in an ad-hoc manner. Let us include as many relevant options as we can find.

water_tags = {'natural':['spring','water'],
              'water':['pond','lake','reservoir'],
              'landuse':['reservoir','basin'],
              'amenity':['watering_place','drinking_water','water'],
              'waterway':'stream',
              'man_made':'water_tank'
        }

### ###########  FIND WATER SOURCES    #############
bbox_gila = (34.35704, 32.77935, -107.65983, -108.94574) # rough box of Gila NF
wgdf = ox.features.features_from_bbox(bbox=bbox_gila, tags=water_tags)

The water features are imported as polygons. That is probably more detail than we need, so let’s reduce the polygon to a point, with the view of being able to import a list of waypoints.

# I really did not want to learn about GeoPandas, but there you go...
water_points = wgdf.copy()
water_points['geometry'] = water_points['geometry'].centroid

We understand that reducing from polygons to points is an approximation, and might not work so well for rivers and streams and large lakes. Still, we approximate for convenience, and see if the results are close enough to be useful.

We should be able to convert the GeoDataFrame back to a “network” with only nodes and no edges, but our OSMnx library functions do not work well with “networks” with no edges. We could add one dummy edge, or just stay with GeoDataFrames. Eventually we are able to plot results on our map.

Zoom in…

With more than 9k water sources in the Gila imported, this is too many to be practical. Some waypoints seem to be multiple locations along a creek. Other points are so numerous in areas, that we begin to doubt their veracity.

Let us filter out all water sources that are more than X distance from our route.

WJ = WG.copy()

E,D = ox.distance.nearest_edges(JD,X=water_points['x'],Y=water_points['y'],return_dist=True)

max_distance = 2000 #meters
EARTH_RADIUS_M = 6_371_009 # new convenience notation for big numbers 
max_distance = max_distance / EARTH_RADIUS_M

# Not clear from documentation, but nearest_edges returns distance in units of
# earth radius if projection is lat/long.
for node,d in zip(WG.nodes(),D):
    if d > max_distance:
        WJ.remove_node(node)

With the distance filter, our water sources are down to 77. Here they are on the map, and when you hover over a source, it shows some description of the water.

We can also plot the water sources with our route. Water sources appear as darker blue dots.

We could try to add these water sources to our databook, but perhaps the easiest approach is just to import water source waypoints to our favorite GPX app.

Download source code here.

Related Posts:

Trails and Graph Theory 17: Dog Food

In the tech world, “eating your own dog food”, or “dogfooding“, is the practice of developers using their own products, as a form of quality control, and for insight whether one is building the right tool.

In this series, we have been discussing the graph theory problem of finding a long continuous loop in the Gila that does not repeat segments, and finally we have a path that is worthy of a long hike. This Spring (2024) I will attempt to hike this route, and see what problems and adventures are encountered. https://sagebrush-trails.com/wp-content/uploads/2024/03/map_gxl.html

[link to map full screen]

In the next few weeks I will post a few more entries about my trails graph theory effort, in preparation for the hike. In April, you will find me on the trail, provisionally named the Gila x Loop: GxL.

I recently learned that the Gila National Forest plans to designate their own loop trail to commemorate the Centennial of the Gila Wilderness. So my first naming choice, GCL Gila Centennial Loop, may be preempted.

Friends and family have helped suggest other possible names: GTL Gila Tangle Loop, GGL Great Gila Loop, GEL Gila Euler Loop, GGT Great Gila Tangle, GIL Gila Infinite Loop, GLL Gila Loop-de-Loop, GLL Gila Long Loop, GSL Gila Strange Loop, GRL Gila Random Loop, GAL Gila Algorithmic Loop, GNL Gila Nerd Loop, Gila Geek Loop, GCL Gila Crazy Loop, GWL Gila Weird Loop. What would you suggest? Perhaps we should stick with GxL, where x is an iterator over all name variants…

As with all my annual long hikes since 2012, you can follow my hiking journal on this blog.

Source code may be downloaded here.

Additional thoughts:

  • Yes, this trail does borrow heavily from the GET and CDT. Those trails already worked to establish long tracks, and my algorithm randomly found their results. I do hope this route offers attractions for hikers who already completed those trails.
  • I did add Pie Town to the loop, by including a rather long road walk, for sentimental reasons, and partly because my adopted section of the CDT is on this route. The town is also included to emphasize that the Gila really does extend almost as far north as Pie. Hikers should consider Pie Town as optional, and feel free to shortcut across.
  • The Gila may have high water crossings this year, which may change my route.
  • It is not at all obvious that long-distance hikers will be attracted to a trail that does not cover a long distance between start and end points. I think the Gila will offer plenty of variety, but we shall see how the trail is experienced and endured. The route is certainly different in form from most long trails.
  • With each inner loop, a hiker has a choice of which direction to go. Do I follow the default path randomly chosen by the algorithm, or make my own choice at each 4-way intersection not previously visited? It would be useful to add tools to define my own choices ahead-of-time to make the best route to space out supply points more equally and modify a databook. Alas, these tools have not been developed yet.
  • Or perhaps the novelty of choosing somewhat randomly a direction at each fresh 4-way, and not in advance, will have some attraction to other hikers, and distinguish this trail in some way? Could this be a feature / gimmick, so each hiker can traverse the route in a unique way, a choose-your-own-adventure hike?
  • Perhaps an app-of-the-future could handle the choose-your-own-adventure hike by automagically reconfiguring each time you made a choice at a fresh intersection?
  • An earlier version included Kingston, and I really wanted to keep that community on-route. But a later routing bypassed that place. I really need to learn a OSM editor app, to make small corrections and add towns back in.
  • Getting ready for the hike shows the need for several other tools, besides a trail editor. The databook needs to be able to be generated starting at any location, and either direction. Also, the Euler path has more than one solution, and could change depending on small variations in the graph. It might be best to enforce the option to go straight through 4-way intersections as default.
  • Another tool that I wanted to get working before starting the hike, but ran out of time, is to import all water (tank, creek, spring, etc) features and add them to the GPX and the databook.
  • I know several people that have deep knowledge of Gila trails. By selecting a route via an algorithm, I fully acknowledge that some of the trails included might not be the best choices.
  • Hike the GxL at your own risk. Do not try unless you have done several other long trails, alone.
Related Posts: