A collection of twitter bots that sequentially post every mile of a trail, road, or path to Twitter.
Path | Distance |
---|---|
Appalachian Trail | 2120 |
Blue Ridge Parkway | 469 |
A series of scripts are run on a GeoJSON file representing the entire length of a trail. The following steps assume you have:
- added a new trail's full geometry as
all.geojson
in the/geom/{identifier}
directory, whereidentifier
is a short string representing the trail's name (at
=>Appalachian Trail
,brp
=>Blue Ridge Parkway
). - Added constants for this new trail in
/src/constants.ts
including total trail distance, Mapbox map id, and mile indicator buffer (seen on images).
This walks the entire trail geometry mile-by-mile and creates .geojson
files for each, represented as a GeoJSON Feature. Initially the properties
for this feature is an empty object, to be populated by subsequent scripts.
A trail identifier must be passed as a parameter when running this script.
npx ts-node src/scripts/01_create_mile_sections.ts at
This iterates every mile of the trail by reading its GeoJSON Feature (/geom/{identifier}/mile_{number}.geojson)
. Once the trail is loaded we use Mapbox's reverse geocode API to convert the first coordinate's (start of mile) latitude and longitude into a set of place identifiers. The API returns an array of definitions for how to describe this place (country, city, park, zip code, etc). Instead of parsing and processing all of these definitions in this step we just save them all to be processed at time-of-post. Async's series is used together with setTimeout
to (hopefully) keep from hammering the API and getting a bad response.
A trail identifier must be passed as a parameter when running this script.
npx ts-node src/scripts/02_add_city_property.ts at
Again, iterating each mile one at a time we series
and setTimeout
to rate-limit requests to the Mapbox API. This is the most complex of the scripts. It iterates over each coordinate in the LineString representing the mile and adds a new elevations
member of properties
representing the elevation for each coordinate. This works by using Mapbox's Terrain-RGB data set and doing some clever tile math over in /src/elevation.ts
. The cleverness wasn't mine, but I borrowed it from this repo. I simply added the ability to cache tiles so that we didn't make an API request for every single coordinate over a trail thousands of miles long.
A trail identifier must be passed as a parameter when running this script.
mkdir tmp
mkdir tmp/elevation_images
npx ts-node src/scripts/03_add_elevations.ts at
This step simply uses the elevation data from the previous step and does some simple math to find minimum elevation, maximum elevation, and total elevation gain (or loss).
A trail identifier must be passed as a parameter when running this script.
npx ts-node src/scripts/04_add_elevation_stats.ts at
Using the same rate-limiting in the previous steps, iterate through each mile and use the Mapbox API to create a static map image and save it to disk. The only interesting thing we are doing here is buffering the LineString representation of a mile to create a Polygon that highlights this specific mile. Each map image is saved to disk under /images/{identifier}/mile_{number}.png
. It's easier to go ahead and pre-render these so that each Github action job runs quickly. After this step is done we're almost ready to post.
A trail identifier must be passed as a parameter when running this script.
npx ts-node src/scripts/05_generate_maps.ts at
You'll need 4 credentials for each bot, stored in a .env
file. A .env-sample
file is provided as an example. These values will automatically be read into environment variables at runtime for use in the Twitter client. Note the _{identifier}
pattern after each of the 4 credentials. These should match the trail identifier value used in scripts and configuration files.
TWITTER_APP_KEY_brp=
TWITTER_APP_SECRET_brp=
TWITTER_ACCESS_TOKEN_brp=
TWITTER_ACCESS_SECRET_brp=
This step assumes you have set up a new GitHub workflow at /.github/workflows/{identfier}.yml
representing the new trail.
The tweet_a_mile
script takes care of posting to Twitter. This walks through each mile for a trail until it finds one where trail.properties.has_tweeted
is falsey, which indicates this is the next trail to be posted. Once this mile section is determined other properties are checked for displaying location and elevation information. Then we find the corresponding map image (or gif, see below) that should be associated with the tweet, located in /images/{identifier}/mile_{number}.png
.
After a successful post, trail.properties.has_tweeted
is set to true
so we know not to tweet this mile again. Finally, the Commit it
step in the GitHub workflow runs to commit this change, ensuring this mile is marked as having been tweeted and setting us up with clean repo to run again on schedule. See the GitHub actions workflow files (/.github/workflows/{identifier}.yml) for more details about this step.
For each post the bot looks for a gif before the png. Some mile sections are particularly interesting and look great when animated. If a gif is found it takes priority and is shown instead of the png.