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:

4th of July Small Project 2024

Eight NMVFO volunteers came together on a warm day for an off-schedule project at 4th of July Campground, clearing downed trees on an alternate section of 4th of July Trail and Albuquerque Trail.

When we climbed higher in altitude, ponderosa pines gave way to fir trees, which were especially challenging clearing from trails, with their numerous thick branches.

We got our first opportunity to field-test 2-person Crosscut Saw #5, newly sharpened. Good noodles.

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: