Javascript Is Required To Use This Page

About roadsign.pictures

A collection of roadsigns taken from 1996 through the present. The pictures pre-2001 were taken with a 35mm camera and scanned. The pictures post 2001 were taken with a digital cameras of improving picture quality. I am a software engineer by profession, and have a degree in Cartography, with an emphasis in Geographic Information Systems. As such, I have put together a system over the years that organizes the pictures by political jurisdiction, route numbers as well as a mapping interface. The photographs are mostly from the western half of the United States and Canada. Professionally, I normally spend my time working in the .NET, React/Typescript, and Azure technologies. This website, serve a dual purpose of publishing my photographs, but also allows me to experiment with new technologies. The next chunk of work I am looking at is to incorporate some AI tools to help me flag images with glare, low contrast, or other artifacts (rain drops, bug splattered windshield, etc…) that make the images less appealing to look at.

Technology

The application, is a static site powered by the Hugo static site generator. Over the years I have tried to server-side render the site (Django), as well as a SPA approach (React + Go APIs) but I have found that in terms of performance, it is faster to pre-render the entire site all at once. There still are a few dynamic elements on this website: search, and the random feature. Compared to most of the Hugo site examples, this site is really built around the various templating aspects. I’m not sure the developers of Hugo had this particular workflow in mind when they built the tool, but it works perfectly for my needs. The site uses the Tailwind library, and in particular the designs from Flowbite. CSS is generally my weakest development skill, so I have found that a CSS framework can make a professional looking website without me having to spend a lot of time messing with CSS. All of the actual signs photographs on this website are hosted in Cloudflare R2. I had the pictures hosted in AWS S3 and Cloudfront for years, but I found the hosting costs are significantly cheaper in R2. I can leverage the Cloudflare security and bot protections for the images. This setup seems to provide the lowest amount of maintenance, with the highest performance I have found so far.

Random Signs

The random highway signs utilize Cloudflare workers and the Cloudflare Workers KV product to generate random images. The workers are configured to redirect to a random sign, or return JSON to allow the client to handle the embed. I am using the Hono framework for my Cloudflare workers. The worker code does not update very frequently, so I do not include publishing the Cloudflare workers with the main deploy process. I have a separate GitHub action that publishes the workers when I specifically update the code.

The search is powered by a self-hosted instance of Meilisearch in combination with the InstantSearch tools developed by Algolia. The search widgets contain a majority of the JavaScript used on this website.

Vector Tiles / Maps

The mapping feature uses the mbtileserver tool to generate vector tiles at build time. The actual map is powered by Mapbox GL JS and provides a simple way to visualize the signs.

Build Process

The build step is powered by GitHub Actions which is generally able to build and publish the site in under 6 minutes. On each build a whole new site is generated, the search index is updated, the signs (for random) are loaded into a Cloudflare Workers KV, and the vector tiles are rebuilt. I have tried this process with a few different static site generators, but Hugo is far and away the only one that can render the sheer number of pages for this website without crashing. The current page count is approximately 36,000.

  1. Go application is run to generate the raw content that is used to build the site. A serverless PostgreSQL database (with PostGIS extensions for the spatial component), currently hosted by Neon is used to store all the data. Most of the queries are pre-built in views, or stored procedures in the PostgreSQL instance. The build script outputs Yaml files, and as well as Json that will be used by the other processes. The yaml files are output into the content directory of the Hugo site. The only content page that actually has static content that is saved into the GitHub repository is this about page. The rest of the content is generated from the Go application.
  2. Hugo is run to generate the static site from the content directory. I am currently hosting the website using Fly.IO using gostatic. Previously I have tried different CDN or hosting technologies like (AWS S3/Cloudfront), or Netlify, but I found that the time it takes to sync and upload the new content is prohibitive - typically upwards of 20 minutes. With Fly.IO it is taking about 3 minutes to publish the entire static site.
  3. A Rust application is used to consume a Json artifact generated by Step 1, and re-builds the entire search index. I used to update the index, one sign at a time, as I added a sign to the database. But now that I am pushing the website as a static website, the search index would get out of sync between the time I added the sign, and the time the sign was actually published into the new site. It is just fast enough now to rebuild the entire index on each build. It takes about 45 seconds to re-index 16,000+ signs currently. In terms of monthly cost, the Meilisearch instance is the most expensive. I recently moved this application from Azure to Fly.IO.
  4. Cloudflare Workers KV is updated with the new signs from an artifact generated in Step 1. The Workers KV data is broken down by political jurisdiction, so I can generate random signs by country, state, county, or from the pool of all signs. I use the Cloudflare Wrangler utility to update the Workers KV data. This takes about 16 seconds to update the entire Workers KV store. The lookups from Workers KV are blazing fast.
  5. The vector tiles are generated using artifacts from Step 1. I use the ogr2ogr tool published as part of the GDAL suite to generate the vector tiles. The tiles are then loaded into mbtileserver. This whole process runs inside a Docker build step, so I do not need to install any of the GDAL dependencies into my build agent. The Docker container with the vector tiles are then hosted in Fly.IO. This whole process takes about 30 seconds.
Version: v1.0.72
Total Signs: 17645

Signs By Country/State

By Date Taken