CLR Brands: Where to Buy

CLR (that’s Calcium, Lime and Rust) makes cleaners sold in stores all over the country, but not every store carries every product. So their Kentico site needed a “Where to Buy” tool: pick a product, enter a zip code, choose how far you’re willing to drive (5 to 100 miles), and get a map and a list of nearby retailers that actually stock it. I built it, and it’s still one of my favorite little projects, mostly because of the math.

clrbrands.com has since been rebuilt on a different platform, so this links to an October 2018 archive of the locator.

The math I still think about

The Earth isn’t flat, so you can’t treat latitude and longitude like points on graph paper and measure a straight line between them. The trick is to go through the middle instead. Draw a line from Earth’s core to the center of the zip code, and another from Earth’s core to the store. Both lines are exactly one Earth radius long, so the only thing that changes from store to store is the angle between them.

The spherical law of cosines turns two latitude and longitude pairs into that angle. Multiply the angle by Earth’s radius (3,959 miles) and you get the distance along the surface, as the crow flies (assuming she’s on her way to buy CLR).

Measuring distance along the Earth's surface A circle represents the Earth. One line runs from Earth's core to the center of the searched zip code, and another runs from Earth's core to a store. Both lines are Earth's radius, 3,959 miles. The angle between them, theta, is found with the spherical law of cosines, and the distance along the surface between the two points is the radius multiplied by that angle. r = 3,959 mi θ Zip code center Store Earth’s core 1. Two points, one center zip code and store, each joined to Earth’s core 2. Find the angle θ spherical law of cosines, right in the SQL 3. Distance = radius × θ 3,959 miles × θ, measured along the surface
Both radii are the same length, so the angle between them is all you need. Arc length is radius times angle.

One query does the work

The best part is where the math runs: inside the database query itself. The store data lived in Kentico custom tables (a product list, a retailer table with a yes/no column for each product, and every store location with its coordinates). A single query works out the distance to every store, keeps the ones inside the chosen radius that carry the chosen product, sorts them nearest first and returns the closest 25. No looping through stores in C#: the database measures, filters and sorts in one pass.

SQL
-- distance in miles from the zip code center to each store
3959 * acos(
    cos(radians(@zipLat)) * cos(radians(store.latitude))
  * cos(radians(store.longitude) - radians(@zipLng))
  + sin(radians(@zipLat)) * sin(radians(store.latitude))
)
The distance expression from the query, tidied up. The original spliced the coordinates straight into the SQL string. Today I’d pass them in as parameters, like this.

Google Maps, three ways

Zip code to coordinates

The math needs latitude and longitude, and people type zip codes. So before the form submits, the browser asks Google’s Geocoding API for the center of that zip code, waits for the answer, and sends the coordinates along in hidden fields.

  • Google APIs
  • JavaScript
  • jQuery

A map that frames itself

Results show up on a Google map with branded pins lettered A through Z (doubling up after that), a home marker for the zip code, and hover cards with the address, phone number and distance. Clicking a pin opens that address in Google Maps.

My favorite detail is the zoom. The map draws an invisible circle around the zip code, sized to the farthest result (miles × 1,609 to get meters), and fits itself to that circle. Whether the stores are 2 miles away or 80, the map frames exactly the results and nothing else.

  • Google APIs
  • JavaScript

Search from anywhere

Every product page had its own “Where to buy” link that passed the product along, so the form arrived already filled in. If a product had no store data yet, the page said so and pointed to online retailers instead of showing an empty map. The search form worked from any page on the site, carrying its values through the session to the results page, where the list paged itself five at a time with letters matching the pins.

  • C#
  • ASP.NET
  • Kentico
  • JavaScript

Looking back

This came from my first couple of years at Sandstorm, and it’s the project that taught me geometry has a place in web development. Once you’ve pictured the circle, the angle and the arc, you don’t forget them.

Stack & skills

  • Kentico
  • ASP.NET Web Forms
  • C#
  • SQL
  • Google Maps JavaScript API
  • Google Geocoding API
  • jQuery
  • Sass
  • ASP.NET
  • Google APIs
  • JavaScript