How to efficiently revisit locations of numerous Observations?

To start, one way to get the information you need is using the iNaturalist export tool.

If you want to do any processing on that information, though, you’ll be better off using the iNaturalist API, specifically the GET /observations endpoint. You can see an example of some raw observation data from your project here.

I maintain pyinaturalist, which is a tool that helps work with this information in the python programming language. Here’s an example query to get the relevant observations from your project, with comments explaining what each parameter does:

from pyinaturalist.node_api import get_all_observations
observations = get_all_observations(
  project_id=36883,         # ID of the 'Sugarloaf Ridge State Park' project
  created_d1='2020-01-01',  # Get observations from January 2020...
  created_d2='2020-09-30',  # ...through September 2020 (adjust these dates as needed)
  geo=True,                 # Only get observations with geospatial coordinates
  geoprivacy='open',        # Only get observations with public coordinates (not obscured/private)
)

See the documentation for more search parameters you can use.

Now this endpoint returns a TON of information, basically everything needed to produce what you see on an Observation page on iNaturalist. You’ll probably only want a small subset of this information. Here’s an example to pare it down to just the basics, to make it easier to look over:

def simplify_observation(obs):
    """Extract only a few basic fields from an observation record"""
    # Start by getting some top-level values
    relevant_keys = ['id', 'observed_on', 'description'] 
    simplified_obs = {k: obs[k] for k in relevant_keys} 

    # Get some nested values
    simplified_obs['taxon'] = '{}: {} ({})'.format(
        obs['taxon']['rank'],
        obs['taxon']['name'],
        obs['taxon']['preferred_common_name'],
    )
    simplified_obs['coordinates'] = obs['geojson']['coordinates'] 

    # Grab the link to the first photo, if any; use medium size instead of thumbnail
    if obs['photos']:
       simplified_obs['photo_url'] = obs['photos'][0]['url'].replace('square', 'medium')
    return simplified_obs

Then if you call that function on your observations you’ll get something like this:

simplified_observations = [simplify_observation(obs) for obs in observations]
print(simplified_observations[0])
{'id': 58211670,
 'observed_on': '2020-08-29',
 'description': None,
 'taxon': 'species: Alnus rhombifolia (white alder)',
 'coordinates': [-122.508701, 38.436655],
 'photo_url': 'https://static.inaturalist.org/photos/92915769/medium.jpeg?1598927538'}

The next step is to convert this into something you can import into a map viewer, which we can use gpxpy for.

import gpxpy

def convert_observations_to_gpx(observations):
    # Create a new GPX file, track, and segment:
    gpx = gpxpy.gpx.GPX()
    gpx_track = gpxpy.gpx.GPXTrack()
    gpx.tracks.append(gpx_track)
    gpx_segment = gpxpy.gpx.GPXTrackSegment()
    gpx_track.segments.append(gpx_segment)

    # Add observation coordinates
    for observation in observations:
        lat, long = observation['geojson']['coordinates']
        gpx_segment.points.append(gpxpy.gpx.GPXTrackPoint(lat, long))

    # Save to file
    with open('observations.gpx', 'w') as f:
        f.write(gpx.to_xml())

convert_observations_to_gpx(observations)

Then if you run your observations through that (no need to also use the simplify_observation() function from above, that was just to make things easier to read), you’ll get a file that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" version="1.1" creator="gpx.py -- https://github.com/tkrajina/gpxpy">
  <trk>
    <trkseg>
      <trkpt lat="-122.510986" lon="38.4358649722"></trkpt>
      <trkpt lat="-122.508701" lon="38.436655"></trkpt>
      <trkpt lat="-122.509084" lon="38.435901"></trkpt>
      <trkpt lat="-122.5094054719" lon="38.45397786"></trkpt>
      ...
    </trkseg>
  </trk>
</gpx>

Which you should be able to view in GaiaGPS. There are several pieces missing from this, including:

  • Adding additional observation details to the GPX metadata
  • Calculating the shortest path to visit all observations and sorting the points in the GPX file
  • An easy way to take notes on observations as you visit them

It’s a start, though. This is just one way to go about it, and there may be other tools out there that can do this kind of thing for you without having to write code, but this is what I happen to be familiar with.

6 Likes